题目
Description
Farmer John 马戏团的 N 头奶牛( 1 ≤ N ≤ 10^5 )正在准备她们接下来的演出。演出在一棵结点编号为 1 … N 的树上进行。演出的“起始状态”可以定义为一个整数 1 ≤ K ≤ N 以及奶牛 1 … K 在树上的结点分布,使得没有两头奶牛位于相同的结点。 在一场演出中,奶牛们可以进行任意次数的“移动”。在一次移动中,一头奶牛从她的当前所在结点移动到一个未被占据的相邻结点。称两个起始状态是等价的,如果一个状态可以通过一系列移动到达另一个。
对于每一个 1 ≤ K ≤ N ,帮助奶牛们求出起始状态的等价类数量:即可选出的起始状态的最大数量,使得两两不等价。由于这些数字可能很大,输出模 10^9 + 7 的余数。
Input
文件名:circus.in
输入的第 1 行包含 N 。 第 2 ≤ i ≤ N 行每行包含两个整数 a i 和 b i ,表示树上连接 a i 和 b i 的一条边。
Output
文件名:circus.out
对于每一个 1≤i≤N,在第 i 行输出 K=i的答案模 10^9+7 的余数。
Sample Input
输入样例1:
5
1 2
2 3
3 4
3 5
输入样例2:
8
1 3
2 3
3 4
4 5
5 6
6 7
6 8
Sample Output
输出样例1:
1
1
3
24
120
输出样例2:
1
1
1
6
30
180
5040
40320
Data Constraint
测试点性质:
测试点 3-4 满足 N≤8。
测试点 5-7 满足 N≤16。
测试点 8-10 满足 N≤100 并且树组成了一个“星形”;至多一个结点的度大于二。
测试点 11-15 满足 N≤100。
测试点 16-20 没有额外限制。
Hint
对于 K=1 和 K=2,任意两个状态之间都可以相互到达。
考虑 K=3,令 ci 为奶牛 i的位置。状态 (c1,c2,c3)=(1,2,3)等价于状态 (1,2,5) 和 (1,3,2),然而不等价于状态 (2,1,3)。
思路
首先k>=n-1的答案都是n!
考虑两个元素 (x,y),若它们能够通过若干次操作互换位置,且不影响其余元素,则称 (x,y) 是一个等价类
显然这具有传递性
假设等价类的大小为si
那么ans=N!/(πsi)
考虑怎么计算一个等价类的大小。考虑判断能不能交换两个位置上的奶牛,然后使得剩下的维持原状。
先考虑一些必要条件,注意到在任意度为 2 的点上不可能完成交换,考虑中间的点的度数都是 2,两端的点的度数都不是 2 的子图,下面我们称它为链。如果一侧子树内有 A 个点,另一侧有 B 个点,链上有 C 个点,那么当 K⩾(A−1)+(B−1),能够到达一侧的 (A−1) 个点始终不能到达另一侧,链上恰好始终有 K−(A−1)−(B−1) 个点。
我们来证明当不违反上述条件时,可以完成交换。即不穿过满足 K⩾(A−1)+(B−1) 的链,能够使得它们互相到达。
先考虑将某一个奶牛移动到右侧。先考虑两端的度数都大于等于 3 的情形。
假设左侧在链上有 l 个点,子树内(不含根)有 a 个点,右侧链上有 r 个点,右侧子树内有 b 个点。根据条件有 a+l+r+b+1<(A−1)+(B−1)。
如果 r+b<B−1 直接移过去就完事了。
否则有 a+l<A−2,
如果和奶牛相连的空位子形成一条链,那么找一个子树内不在链上的奶牛移到链上,然后把这个奶牛移动到这个位置上,然后把剩下的奶牛往子树内移动,直到 r+b<B−1。
否则随意移动到一棵子树内,然后将在链上的下一个点移开,直到形成一条链或者 r+b<B−1。
当一端度数为 1,有 r+b+1<B−1,显然可行。
然后考虑完成交换,这个时候只用证明任意两个链上相邻的奶牛可以交换位置就可以了。此时满足 a+l+r+b+2<(A−1)+(B−1)。即 a+l+r+b<A+B−4,此时要么 a+l⩽A−3,要么 b+r⩽B−3。不妨设是 a+l⩽A−3,这个时候只用将 l 个奶牛全部移动到子树内,如果使得空的位置形成了一条链,那么把一个不在链上的奶牛,移到链上就可以了。然后再子树中剩下至少 3 个空位可以完成交换。
现在来说明一下可以使得剩下的奶牛位置不变,注意到操作总是可逆的,x,y 交换完成后,把 y 看成 x,把 x 看成 y,因为存在初始状态到达它的方案,所以也存在它到初始状态的方案,操作结束后 x,y 的位置是相反的。
如果两个位置能交换,那么它们连一条边,sz=∏si!,si 是每个连通块的大小。
然后从大到小枚举 K ,如果一条链满足 K<(A−1)+(B−1) 就把它两端连接上。然后问题变成能够到达每个连通块的有多少点。直接做不好做,但根据和它相邻的被断掉的边计算不能到它的点有多少个,把它们减去就行了。
另外注意到连通块数等于链数加一,一条链在存在的次数等于它的链长,所以对于每个 K 暴力枚举所有连通块复杂度为 O(n)。剩下的并查集维护即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+77,mod=1e9+7;
ll power(ll x,ll t)
{
ll b=1;
while(t)
{
if(t&1) b=b*x%mod;
x=x*x%mod; t>>=1;
}
return b;
}
int n,fac[N],inv[N];
void init(int n)
{
fac[0]=inv[0]=1;
for(int i=1; i<=n; i++)
{
fac[i]=1ll*fac[i-1]*i%mod;
inv[i]=power(fac[i],mod-2);
}
}
set<int> key;
pair<int,int> info[N];
vector<int> a[N];
int f[N],ans[N];
vector<pair<int,pair<int,int> > > paths;
int gf(int x)
{
if(f[x]==x) return x;
else return f[x]=gf(f[x]);
}
void merge(int x,int y,int z)
{
x=gf(x),y=gf(y);
if(x==y) return;
key.erase(y),f[y]=x;
info[x].first+=info[y].first+z-1;
info[x].second+=info[y].second-2;
}
void work(int pos,int fa,int from,int depth)
{
if(a[pos].size()!=2)
{
if(from!=0) paths.emplace_back(depth, make_pair(pos, from));
depth=1,from=pos;
}
for(auto x : a[pos])
if(x!=fa)
{
work(x,pos,from,depth+1);
}
}
int main()
{
freopen("circus.in","r",stdin);
freopen("circus.out","w",stdout);
scanf("%d",&n);
init(n);
for(int i=1; i<=n-1; i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
for(int i=1; i<=n; i++)
if(a[i].size()!=2)
{
key.insert(i),f[i]=i;
info[i].second=a[i].size();
}
work(*key.begin(),0,0,1);
sort(paths.begin(),paths.end());
for(int k=n-1; k>=1; k--)
{
unsigned int pos=0;
while(pos<paths.size()&&k<n-paths[pos].first)
{
merge(paths[pos].second.first,paths[pos].second.second,paths[pos].first);
pos++;
}
int res=fac[k];
for(auto x:key)
{
int tmp=k-(n-1-info[x].first)-info[x].second*(k-n+1);
res=1ll*res*inv[tmp]%mod;
}
ans[k]=res;
}
ans[n]=fac[n];
for(int i=1; i<=n; i++)
printf("%d\n",ans[i]);
}