青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)
Posted chenhuan001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)相关的知识,希望对你有一定的参考价值。
1.对于简单的版本n<=500, ai<=50
直接暴力枚举两个点x,y,dfs求x与y的距离。
2.对于普通难度n<=10000,ai<=500
普通难度解法挺多
第一种,树形dp+LCA
比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2
然后找出所有互质的对数,然后对为1的数进行特殊处理。(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7)
对所有的非1质数对,采用离线LCA可以搞定。
对于1的特殊情况,只需要用两次dfs,就可以找出所有1到其它点的距离和与1之间的距离和。
第二种,树形dp+容斥
这种方法从边的角度,考虑每一条边会被计算多少次,这也是树上求距离的常用方法。
由于树边都是桥边,所有只要求出边两边联通块之间互质对数。最简单的想法即是枚举每一条边,然后再分别枚举两边区域,这样的复杂度是500*500*10000 很遗憾并没有这么简单。于是用容斥原理进行优化。在枚举某条边的一边的x(1<=x<=500)的时候,考虑右边为x质因子倍数的情况,也就是容斥了。 这样可以将复杂度变为10000*500*k*2^k( k<=4)
官方题解:
附上代码:
// // main.cpp // 160701 // // Created by New_Life on 16/7/1. // Copyright © 2016年 chenhuan001. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> #include <algorithm> using namespace std; #define N 10100 vector<int> save[505]; int g[N]; struct node { int to,next; }edge[2*N]; int cnt,pre[N]; int dp[N][505]; int savecnt[N][505]; int cntall[505]; long long ans; void add_edge(int u,int v) { edge[cnt].to = v; edge[cnt].next = pre[u]; pre[u] = cnt++; } void dfs(int s,int fa) { for(int p=pre[s];p!=-1;p=edge[p].next) { int v = edge[p].to; if(v == fa) continue; dfs(v,s); for(int i=1;i<=500;i++) { dp[s][i] += dp[v][i]; savecnt[s][i] += savecnt[v][i]; } } savecnt[s][ g[s] ]++; for(int i=0;i<(1<<save[g[s]].size());i++) { int tmp = 1; for(int j=0;j<save[g[s]].size();j++) { if(((1<<j)&i) != 0) { tmp *= save[g[s]][j]; } } dp[s][tmp]++; } //int last[505]; int lastcnt[505]; for(int p=pre[s];p!=-1;p=edge[p].next) { int v = edge[p].to; if(v == fa) continue; for(int i=1;i<=500;i++) { //last[i] = all[i]-dp[v][i]; lastcnt[i] = cntall[i]-savecnt[v][i]; } //对这条边进行处理 for(int i=1;i<=500;i++) { if(lastcnt[i] == 0) continue; for(int j=0;j<(1<<save[i].size());j++) { int tcnt=0; int tnum = 1; for(int k=0;k<save[i].size();k++) { if( ((1<<k)&j)!=0 ) { tcnt++; tnum *= save[i][k]; } } if(tcnt%2 == 0) ans += lastcnt[i]*dp[v][tnum]; else ans -= lastcnt[i]*dp[v][tnum]; } } } } int main(int argc, const char * argv[]) { for(int i=1;i<=500;i++) { int ti = i; for(int j=2;j*j<=ti;j++) { if(ti%j == 0) { save[i].push_back(j); while(ti%j==0) ti/=j; } } if(ti != 1) save[i].push_back(ti); } //for(int i=1;i<=500;i++) printf("%d\\n",save[i].size()); cnt = 0; memset(pre,-1,sizeof(pre)); int n; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",g+i);//得把每一项都变成最简单 int tg =1; for(int j=0;j<save[ g[i] ].size();j++) { tg *= save[ g[i] ][j]; } g[i] = tg; } for(int i=1;i<n;i++) { int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } ans = 0; for(int ii=1;ii<=n;ii++) { cntall[ g[ii] ]++; } dfs(1,-1); cout<<ans<<endl; return 0; }
3. 对于困难难度
这就需要用到虚树这种没听过的东西了,百度学习下,然后发现原理还是很简单的。应用情景,对于一颗树,挺大,但是需要操作的结点不多,这时候把需要操作的结点重新建一颗小的树(需要用的信息不能少)。
思路概述:(抄了下)
- 枚举 因数x,x是每种质因子至多有一个的数,记录一下x有几种质因子,方便之后容斥。
- 把所有x的倍数的权值的点找出来,预处理下可以做到找出来的点的dfs序是从小到大的,预处理也可以使得每次找x的倍数的权值的点不必线性扫一遍。
- 然后对这些点 O(n) 建虚树,具体操作是相邻两个点加进去 lca,用一个栈维护下父亲链即可。[bzoj3572]是一道典型的虚树的题目。
- 构建好树后在树上 dfs 两次可以求出所有x的倍数的权值的点对之间的距离和,就是第一遍dfs记录以节点u为根的子树中,有多少个x倍数的点(可能有一些是虚树添加进来的lca点),第二遍dfs其实是枚举每条边,计算(u,v)这条边的总价值,就是它出现的次数乘以它的权值;它出现的次数就是它子树中x倍数的点的个数,乘以不在它子树中x倍数的点的个数。
- 最后容斥下就可以求出答案。
由于所有步骤均是线性的,而所有虚树加起来的总点数也是线性乘上一个常数的,所以复杂度为 O(nK),K<=128。
对于复杂度分析,我抱有不同的看法,上述过程中建虚树是O(nlog(n))的,100000以内不重复质数最多是6个,所以最大复杂度为O(64*n*log(n))
// // main.cpp // Xushu // // Created by New_Life on 16/7/1. // Copyright © 2016年 chenhuan001. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> #include <algorithm> using namespace std; #define N 100100 #define LN 20 struct node { int to,next; }edge[2*N]; int cnt,pre[N]; void add_edge(int u,int v) { edge[cnt].to = v; edge[cnt].next = pre[u]; pre[u] = cnt++; } int deep[N]; int g[N];//记录每个点的权值 vector<int>saveall[N];//记录i所有的倍数 int sign[N]; int len[N];//每个点到根的距离 int mark[N];//标示虚树上的点是否是无用点 struct Lca_Online { int _n; int dp[N][LN]; void _dfs(int s,int fa,int dd) { int factor[30]; int fcnt=0; int tmp = g[s]; for(int i=2;i*i<=tmp;i++) { if(tmp%i == 0) { factor[ fcnt++ ] = i; while(tmp%i == 0) tmp/=i; } } if(tmp != 1) factor[ fcnt++ ] = tmp; for(int i=0;i<(1<<fcnt);i++) { tmp = 1; int tsign = 1; for(int j=0;j<fcnt;j++) if( ((1<<j)&i) != 0 ) { tmp *= factor[j]; tsign *= -1; } saveall[tmp].push_back(s); sign[tmp] = tsign; } deep[s] = dd; for(int p=pre[s];p!=-1;p=edge[p].next) { int v = edge[p].to; if(v == fa) continue; _dfs(v,s,dd+1); dp[v][0] = s; } } void _init() { for(int j=1;(1<<j)<=_n;j++) { for(int i=1;i<=_n;i++) { if(dp[i][j-1]!=-1) dp[i][j] = dp[ dp[i][j-1] ][j-1]; } } } void lca_init(int n) { _n = n; memset(dp,-1,sizeof(dp)); //_dfs(firstid,-1,0); _dfs(1,-1,0); _init(); } int lca_query(int a,int b) { if(deep[a]>deep[b]) swap(a,b); //调整b到a的同一高度 for(int i=LN-1;deep[b]>deep[a];i--) if(deep[b]-(1<<i) >= deep[a]) b = dp[b][i]; if(a == b) return a; for(int i=LN-1;i>=0;i--) { if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i]; } return dp[a][0]; } }lca; int stk[N],top; vector<int>tree[N];//存边 vector<int>treew[N];//存权 void tree_add(int u,int v,int w) { tree[u].push_back(v); tree[v].push_back(u); treew[u].push_back(w); treew[v].push_back(w); } long long down[N]; long long ccnt[N]; long long sum[N]; int nn; void dfs1(int s,int fa) { down[s] = 0; ccnt[s] = 0; for(int i=0;i<tree[s].size();i++) { int to = tree[s][i]; if(to == fa) continue; dfs1(to,s); down[s] += down[to] + ccnt[to]*treew[s][i]; ccnt[s] += ccnt[to]; } if(mark[s]==1) ccnt[s]++; } void dfs2(int s,int fa,long long num,long long tcnt) { sum[s] = down[s]+num+tcnt; for(int i=0;i<tree[s].size();i++) { int to = tree[s][i]; if(to == fa) continue; dfs2(to,s,sum[s]-down[to]-ccnt[to]*treew[s][i],(nn-ccnt[to])*treew[s][i]); } } int main(int argc, const char * argv[]) { cnt = 0; memset(pre,-1,sizeof(pre)); int n; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",g+i); } for(int i=1;i<n;i++) { int a,b; scanf("%d%d",&a,&b); add_edge(a, b); add_edge(b, a); } lca.lca_init(n); long long ans=0; for(int x=1;x<=100000;x++) { if(saveall[x].size() == 0) continue; //build virtual tree top = 0; stk[top++] = saveall[x][0]; tree[ saveall[x][0] ].clear(); treew[ saveall[x][0] ].clear(); mark[saveall[x][0]]=1; for(int i=1;i<saveall[x].size();i++) { int v = saveall[x][i]; int plca = lca.lca_query(stk[top-1], v);//最近公共祖先 if(plca == stk[top-1]) ;//不处理 else { int pos=top-1; while(pos>=0 && deep[ stk[pos] ]>deep[plca]) pos--; pos++; for(int j=pos;j<top-1;j++) { tree_add(stk[j],stk[j+1],deep[stk[j+1]]-deep[stk[j]]); } int prepos = stk[pos]; if(pos == 0) { tree[plca].clear(),treew[plca].clear(),stk[0]=plca,top=1; mark[plca] = 0; } else if(stk[pos-1] != plca) { tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+1; mark[plca] = 0; } else top = pos; tree_add(prepos,plca,deep[prepos]-deep[plca]); } tree[v].clear(); treew[v].clear(); stk[top++] = v; mark[v] = 1; } for(int i=0;i<top-1;i++) { tree_add(stk[i], stk[i+1], deep[stk[i+1]]-deep[stk[i]]); } //构建好了虚树,然后就是两次dfs nn = (int)saveall[x].size(); dfs1(saveall[x][0],-1); dfs2(saveall[x][0],-1,0,0); long long tans=0; for(int i=0;i<saveall[x].size();i++) tans += sum[ saveall[x][i] ]; tans /= 2; ans += sign[x]*tans; } cout<<ans<<endl;//时间,内存。 return 0; }
以上是关于青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)的主要内容,如果未能解决你的问题,请参考以下文章