【JZOJ 省选模拟】Circus

题目

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]);
}

发布了809 篇原创文章 · 获赞 396 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Eric1561759334/article/details/105465523