有N个点,N-1条边的一棵树,给出它的每条边X,Y,求有多少种剪法剪短其中的某些边使得还连着的每一块都有相同的点数,注意:不能不剪,可以全剪
样例输入:
6
1 2
2 3
2 4
4 5
5 6
样例输出:
2
用邻接表储存图:
cin>>n; for (int i=1;i<=n-1;i++) { cin>>a[i*2]>>b[i*2]; a[i*2-1]=b[i*2]; b[i*2-1]=a[i*2]; if (first[a[i*2]]==0) last[a[i*2]]=first[a[i*2]]=i*2; else last[a[i*2]]=next[last[a[i*2]]]=i*2; if (first[a[i*2-1]]==0) last[a[i*2-1]]=first[a[i*2-1]]=i*2-1; else last[a[i*2-1]]=next[last[a[i*2-1]]]=i*2-1; }
用深搜枚举出每一棵子树的结点数,(这里我竟然对无向图的邻接表琢磨了好久,s[x]表示以x为根的子树的结点数)。
int dfs(int x) { if (vis[x]) return 0; vis[x]=true; int u=first[x]; while (u!=0) { if (!vis[b[u]]) s[x]+=dfs(b[u]); u=next[u]; } return ++s[x]; }
需要注意的是,进入深搜只要写:s[1]=dfs(1);即可。
然后就是最激动人心的部分:
先枚举每块的大小K,有两个显然但是比较难想到的性质:
1.如果某个子树的大小不是K的倍数,那么这个子树的根一定和其父亲在一个连通块内。
证明:否则这个子树就要被分成若干个大小为K的块,但是其大小又不是K的倍数,必定不可能。
2.如果某个子树的大小是K的倍数,那么这个子树的根一定不和其父亲在一个连通块内。
证明:否则这个子树中其它的点都要自己解决,那么这个连通块的点一定也是K的倍数,进一步地,一定恰好是K。但是这么一来,根就不可能再往上多连了。
然后最终结论:如果恰好有N/K个子树的大小是K的倍数,则K是一个可行的方案。
于是预处理出所有子树大小后枚举K,然后每个K暴力统计各个倍数出现了几次即可。
for (k=2;k<=n;k++) if (n%k==0) { tt=0; for (int i=1;i<=n;i++) if (s[i]%k==0) tt++; //cout<<tt<<endl; if (tt==(n/k)) tot++; }