P2351 [SDOi2012]吊灯
题意:
一棵树,能否全部分成大小为x的联通块。
分析:
显然x是n的约数。然后对于一个约数x,判断能否分成 $ \frac{n}{x} $ 个大小为x的联通块。
结论:如果x可以,那么一定存在$ \frac{n}{x} $个节点的子树大小是x的倍数。
证明:上面的结论说明的也就是每个大小是x的倍数的点,对答案的贡献是1(每个点都可以分出一个大小为x的块),加起来就是$ \frac{n}{x} $。
现在就要考虑一个点u的siz是kx,然后它的子树里如果没有其他点的siz的是x的倍数的话,它的贡献是1,它可以从根节点开始,分出一个包含根节点,一共x个点的联通块。
然后考虑u的子树里还有一个点v的siz是x的倍数,那么如果它们还能分成两个大小为x的块的话,那么每个这样的点的贡献还是1。首先从在v的子树里一定可以从根开始分出一个大小为x的块(u在其中),然后u的子树里需要找一个大小为x的块,且不使用v中的点。假设去掉v中的点还剩siz[u]-siz[v]个,这也是x的倍数,所以u的子树里,从根开始,不占用v的点,还可以分出一个大小为x的块。说明u的子树可以贡献2,uv各自贡献1。
如果u的子树还有这样的点,那么把v删掉,还是两个点的情况,所以还是合法的。
到此发现每个大小为x的倍数的点,会对答案贡献1。$ \frac{n}{x} $$个,就会有$ \frac{n}{x} $个大小为x的联通块,如果小于则不行。
代码:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 using namespace std; 12 typedef long long LL; 13 14 inline int read() { 15 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 16 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 17 } 18 19 const int N = 12000005; 20 21 int fa[N], cnt[N], siz[N], n; 22 vector<int> v; 23 24 bool check(int x) { 25 int res = 0; 26 for (int i=x; i<=n; i+=x) res += cnt[i]; 27 return res >= n / x; 28 } 29 30 int main() { 31 n = read(); 32 for (int lim=sqrt(n),i=1; i<=lim; ++i) { 33 if (n % i == 0) { 34 v.push_back(i); 35 if (n / i != i) v.push_back(n / i); 36 } 37 } 38 sort(v.begin(), v.end()); 39 for (int i=2; i<=n; ++i) 40 fa[i] = read(); 41 for (int T=1; T<=10; ++T) { 42 printf("Case #%d:\n",T); 43 for (int i=1; i<=n; ++i) cnt[i] = 0, siz[i] = 1; 44 for (int i=n; i>=1; --i) siz[fa[i]] += siz[i], cnt[siz[i]] ++; 45 for (int i=0; i<v.size(); ++i) 46 if (check(v[i])) printf("%d\n",v[i]); 47 if (T != 10) for (int i=2; i<=n; ++i) 48 fa[i] = (fa[i] + 19940105) % (i - 1) + 1; 49 } 50 return 0; 51 }