Infinite Tree
Infinite Tree
題意:
題解:
參考博客
看了好一陣子才明白。。。emm。
我們先按照題意畫出一部分樹
我們先不考慮復雜度,這題應該怎么做?
題目給了每個點的權值w[i],問一個點到所有的節點路徑長度*點權之和最小是多少,很明顯是樹形dp
dp[i]表示以i為根的子樹到i的w和
sum[i]表示乘上距離之后的答案
dep[i]表示深度,now為當前節點,son為子節點
則有:
但是以now為根并不一定是最佳答案,所以我們還要不斷的換根,求出最佳答案
如果將now為根轉移到son為根,我們不用重新算一遍,只需要在之前的基礎上
這樣就OK了
但是!
題目是要求u到i!的距離,i!的增長速度很快,樹的增長也是很快,如果全建出來肯定TLE了,所以我們沒辦法將整棵樹建立,只能選擇性建立,這要用到虛樹
我們將階乘點當做關鍵點,保留關鍵點及其lca,然后建立數就行
現在又有一個新問題,lca怎么計算?
我們首先考慮a!和(a+1)!的dfn誰大?
因為后者的因子一定包含前者的因子,所以除以mindiv后,(a+1)!的深度肯定更大,所以這些階乘數的dfn是隨a值從小到大的,這樣我們只需要考慮相鄰兩個點的lca即可
我們列一個表格記錄階乘數分解后有多少個質數
我們根據上面的表格來列出相鄰的LCA
2!和3 ! : 1,深度為1
3!和4 ! : 6,深度為3
4!和5 !: 1,深度為1
5!和6 !: 15,深度為3
6!和7 !: 1,深度為1
我們可以得出,對于a!和(a+1)!,LCA就是從大到小公共的質因子的乘積,遇到不同的就停止,深度是相同的個數+1
例如5!和6!:
5!分解后:5 3 2 2 2
6!:5 3 3 2 2 2 2
從大到小,一樣的是5和3 ,(從第三位開始不一樣,停止)
lca就是15,深度就是3
可以得到dep[lca((i+1)!,i!)] = sum(maxdiv(i+1),n)
sum(maxidv(i+1),n)為原本i!中大于等于maxdiv(i+1)的因子個數
這樣我們就可以快速算出LCA
但是還是不夠快,如果對于每個數都掃一次的話,還是很慢。所以,需要一個快速地查找求和,修改的算法,那就是用到了線段樹或樹狀數組
代碼:
#include<bits/stdc++.h> #define ll long long #define inf 1ll<<60 using namespace std; const int MAXN=1e5+10; int num[MAXN],w[MAXN<<1],d[MAXN<<1],stk[MAXN]; int ldfn[MAXN],rdfn[MAXN],dep[MAXN],lcad[MAXN],m; int mndv[MAXN],pcnt=0; ll dp1[MAXN<<1],dp2[MAXN<<1],ans; vector<int> vir[MAXN<<1]; ll tr[MAXN<<2];//注意開long long void Build(int root,int l,int r) {tr[root]=0;if(l==r) return;int mid=l+r>>1;Build(root<<1,l,mid);Build(root<<1|1,mid+1,r); } void Change(int root,int l,int r,int x) {if(l==r){tr[root]++;return;}int mid=l+r>>1;if(x<=mid) Change(root<<1,l,mid,x);else Change(root<<1|1,mid+1,r,x);tr[root]=tr[root<<1]+tr[root<<1|1]; }//x是插入的質數,要將其計數+1 int Query(int root,int x,int l,int r) { if(l>=x) return tr[root];int mid=l+r>>1;if(x<=mid) return Query(root<<1,x,l,mid)+Query(root<<1|1,x,mid+1,r);else if(x>mid) return Query(root<<1|1,x,mid+1,r); }//查找當前質數的個數 void build() {//^不等于,相當于!=dep[1]=1;for(int i=2;i<=m;i++){dep[i]=dep[i-1];int j=i;while(j^mndv[j]) j/=mndv[j];lcad[i]=Query(1,j,1,m)+1;for(j=i;j^1;dep[i]++,j/=mndv[j]) Change(1,1,m,mndv[j]);}int top=0,tot=m;stk[++top]=1;for(int i=2;i<=m;i++){if(top==1||lcad[i]==dep[stk[top]]){stk[++top]=i;continue;}while(top>1&&lcad[i]<=dep[stk[top-1]]){vir[stk[top-1]].push_back(stk[top]);top--;}//建虛樹的基本操作,不會的建議去學習一下if(lcad[i]^dep[stk[top]]){dep[++tot]=lcad[i];w[tot]=0;vir[tot].push_back(stk[top]);stk[top]=tot;}stk[++top]=i;}while(top>1){vir[stk[top-1]].push_back(stk[top]);top--;} }//原理同上,供參考 void dfs1(int x,int fa) {dp1[x]=w[x];dp2[x]=0;for(int i=0;i<vir[x].size();i++){int son=vir[x][i];if(son==fa) continue;dfs1(son,x);dp1[x]+=dp1[son];dp2[x]+=dp2[son]+(dep[son]-dep[x])*dp1[son];} } void dfs2(int x,int fa) {ans=min(ans,dp2[x]);for(int i=0;i<vir[x].size();i++){int son=vir[x][i];if(son==fa) continue;ll x1=dp1[x],x2=dp2[x],son1=dp1[son],son2=dp2[son];dp2[x]-=dp2[son]+(dep[son]-dep[x])*dp1[son];dp1[x]-=dp1[son];dp2[son]+=dp2[x]+(dep[son]-dep[x])*dp1[x];dp1[son]+=dp1[x];dfs2(son,x);dp1[x]=x1,dp2[x]=x2,dp1[son]=son1,dp2[son]=son2;} }//樹形dp+換根,非重點,且是模板一套的問題,上面已經分析 int main() {mndv[1]=1;for(int i=2;i<MAXN;i++)if(!mndv[i])for(int j=i;j<MAXN;j+=i)if(!mndv[j]) mndv[j]=i;//預處理出每個數的mindiv,之后分解時可以用while(~scanf("%d",&m)){for(int i=1;i<=m;i++)scanf("%d",&w[i]);for(int i=0;i<=m*2;i++){vir[i].clear();dp1[i]=dp2[i]=0;}Build(1,1,m);build();dfs1(1,0);ans=dp2[1];dfs2(1,0);printf("%lld\n",ans);} } #include<bits/stdc++.h> #define lowbit(x) x&-x using namespace std; typedef long long ll; const int MAX = 2e5 + 10; //建立虛樹點數tot < 2n, 空間開兩倍int n, w[MAX]; ll ans;//樹狀數組 int c[MAX]; void upd(int p, int k) { for (; p <= n; p += lowbit(p)) c[p] += k; } int query(int p) { int res = 0; for (; p; p -= lowbit(p)) res += c[p]; return res; }int mindiv[MAX]; void sieve(int siz) {//篩mindivfor (int i = 2; i <= siz; i++)if (!mindiv[i])for (int j = i; j <= siz; j += i)if (!mindiv[j])mindiv[j] = i; }int lcadep[MAX], dep[MAX]; int st[MAX], top, tot;//stack, top, tot:虛樹點數 vector<int> g[MAX];//虛樹 void add_edge(int u, int v) { g[u].push_back(v), g[v].push_back(u); }void buildVirtualTree() {tot = n;st[top = 1] = 1;for (int i = 2; i <= n; i++) {dep[i] = dep[i - 1] + 1; int j = i;for (; j != mindiv[j]; j /= mindiv[j]) dep[i]++;lcadep[i] = query(n) - query(j - 1);for (j = i; j != 1; j /= mindiv[j]) upd(mindiv[j], 1);}//建樹for (int i = 2; i <= n; i++) {while (top > 1 && dep[st[top - 1]] >= lcadep[i])add_edge(st[top - 1], st[top]), top--;if (dep[st[top]] != lcadep[i]) {dep[++tot] = lcadep[i];add_edge(tot, st[top]);st[top] = tot;}st[++top] = i;}while (top > 1){add_edge(st[top - 1], st[top]);top--;} }void dfs(int u, int fa) {ans += 1ll * w[u] * dep[u];//ans最開始是rt = 1時的答案for (auto &v: g[u])if (v != fa) {dfs(v, u);w[u] += w[v];} }void dfs2(int u, int fa) {//如果rt移動之后答案變小就一直移動下去,直到答案不在變小for (auto &v: g[u])if (v != fa) {//rt從u轉移到v的代價//+(w[1] - w[v]) - w[v]if (w[1] - 2 * w[v] < 0) {ans += 1ll * (w[1] - 2 * w[v]) * (dep[v] - dep[u]);//一步的代價*距離dfs2(v, u);}} }void init() {ans = top = 0;for (int i = 1; i <= tot; i++) {g[i].clear();c[i] = w[i] = lcadep[i] = dep[i] = 0;} }int main() {sieve(1e5);while (~scanf("%d", &n)) {init();for (int i = 1; i <= n; i++) scanf("%d", &w[i]);buildVirtualTree();int rt = 1;dfs(rt, 0);dfs2(rt, 0);printf("%lld\n", ans);}return 0; }總結
以上是生活随笔為你收集整理的Infinite Tree的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于Java回调的最深刻解析
- 下一篇: 使用IDM解决FTP下载缓慢问题