BZOJ4401:块的计数(乱搞)
Description
小Y最近从同学那里听说了一个十分牛B的高级数据结构——块状树。听说这种数据结构能在sqrt(N)的时间内维护树上的各种信息,十分的高效。当然,无聊的小Y对这种事情毫无兴趣,只是对把树分块这个操作感到十分好奇。他想,假如能把一棵树分成几块,使得每个块中的点数都相同该有多优美啊!小Y很想知道,能有几种分割方法使得一棵树变得优美。小Y每次会画出一棵树,但由于手速太快,有时候小Y画出来的树会异常地庞大,令小Y感到十分的苦恼。但是小Y实在是太想知道答案了,于是他找到了你,一个天才的程序员,来帮助他完成这件事。
Input
第一行一个正整数N,表示这棵树的结点总数,接下来N-1行,每行两个数字X,Y表示编号为X的结点与编号为Y的结点相连。结点编号的范围为1-N且编号两两不同。
Output
一行一个整数Ans,表示所求的方案数。
Sample Input
1 2
2 3
2 4
4 5
5 6
Sample Output
HINT
100%的数据满足N<=1000000。
Solution
我竟然天真的以为$2e8$能跑过去直到我被最大的点卡到$10s$……
感觉也说不上什么算法,就算他是乱搞吧。
一开始洲哥给了一个$n\sqrt{n}$的写法。下一段根号做法可以不看因为我感觉我写的可能比正解还难懂……
我们先枚举当前要分的块大小$k$,再随便找一个根$DFS$一下,从下往上贪心的分,也就是够$k$个就分成一块。感性理解一下还是非常正确的……对于每个点$x$我们求出$(\sum size[son[x]]\%k)+1$,如果存在某个点的这个值大于$k$的话显然就是不合法的。其中$size[son[x]]\%k$也就是$son[x]$这颗子树里面分完了剩下的点。这个为什么是对的我就不多说了……要真想不懂的话可以直接去看下面正解做法。
其实仔细想想,确定了块大小$k$之后,那么每一块内的根的$size$肯定就是$k$的倍数。这个应该还是比较显然的,因为你是从下往上贪心来分的。那么我们直接开个桶记下$size$,对于每一个能被$n$整除的块大小$k$,统计有多少$size[x]$被$k$整除,如果与$n/k$相同则合法。复杂度应该是$O(n+\sqrt{n}logn)$
Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #define N (1000009) 5 using namespace std; 6 7 struct Edge{int to,next;}edge[N<<1]; 8 int n,ans,size[N],u,v,Keg[N]; 9 int head[N],num_edge; 10 11 inline int read() 12 { 13 int x=0; char c=getchar(); 14 while (c<'0' || c>'9') c=getchar(); 15 while (c>='0' && c<='9') x=x*10+c-'0', c=getchar(); 16 return x; 17 } 18 19 void add(int u,int v) 20 { 21 edge[++num_edge].to=v; 22 edge[num_edge].next=head[u]; 23 head[u]=num_edge; 24 } 25 26 void DFS(int x,int fa) 27 { 28 size[x]=1; 29 for (int i=head[x]; i; i=edge[i].next) 30 if (edge[i].to!=fa) 31 { 32 DFS(edge[i].to,x); 33 size[x]+=size[edge[i].to]; 34 } 35 Keg[size[x]]++; 36 } 37 38 int main() 39 { 40 n=read(); 41 for (int i=1; i<=n-1; ++i) 42 { 43 u=read(); v=read(); 44 add(u,v); add(v,u); 45 } 46 DFS(1,0); 47 for (int i=1; i<=n; ++i) 48 if (n%i==0) 49 { 50 int sum=0; 51 for (int j=1; i*j<=n; ++j) 52 sum+=Keg[i*j]; 53 ans+=(sum==n/i); 54 } 55 printf("%d\n",ans); 56 }