6546. 【GDOI2020模拟4.8】USACO 2020 Open Contest, Platinum(circus)

题目描述

Farmer John 马戏团的 N 头奶牛( 1 ≤ N ≤ 10^5 )正在准备她们接下来的演出。演出在一棵结点编号为 1 … N 的树上进行。演出的“起始状态”可以定义为一个整数 1 ≤ K ≤ N 以及奶牛 1 … K 在树上的结点分布,使得没有两头奶牛位于相同的结点。 在一场演出中,奶牛们可以进行任意次数的“移动”。在一次移动中,一头奶牛从她的当前所在结点移动到一个未被占据的相邻结点。称两个起始状态是等价的,如果一个状态可以通过一系列移动到达另一个。
对于每一个 1 ≤ K ≤ N ,帮助奶牛们求出起始状态的等价类数量:即可选出的起始状态的最大数量,使得两两不等价。由于这些数字可能很大,输出模 10^9 + 7 的余数。

N ≤ 10^5

题解

很神奇的题目

猜结论,k只奶牛的位置不影响结果,最大数量只与奶牛间是否能交换有关,若干奶牛可以交换则只算一次

证明不会

所以可以假设k只奶牛都是当前深度最大的,只需要维护是否能交换即可,视作边

选一个度数>=3的点做根,如果没有就是链

关系有根与根之间的,父子间的以及兄弟(父亲不是根节点)间的,其余的关系可通过这三种得到

设编号从下往上从小到大,显然k=n-1和n时答案为k!

当k=n-2时,除编号为n-1的儿子以外,其余的根均相连

k<=n-3时所有根相连

当k<=n-2时兄弟相连

对于每个点x设dis[x]表示x向上到某个度数>=3的点的距离,因为根节点的度数>=3所以必然存在

所以x与儿子相连的条件是dis[x]+2<=n-k,即把向上的一段移开并多出两个来交换

只可能向上,因为如果要在一个非根节点处向另一棵子树走的话,父亲边+走上来的边+走下去的边已经有至少三条了

对于每种情况在考虑一下两端的出现时间,以及根的话要考虑父亲的出现时间,可以建一个0点来连

最后变成若干条边,每条边有出现时间,求每个时间的连通情况,线段树分治+可撤销并查集即可

code

#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define mod 1000000007
#define Mod 1000000005
#define ll long long
#define file
using namespace std;

struct type{int x,y,X,Y,s;} b[300001],S;
int a[200001][2],ls[100001],D[100001],d[100001],num[100001],dis[100001],size[100001];
int fa[100001],dp[100001],n,i,j,k,l,root,len,h,t,tot;
ll jc[100001],Jc[100001],w[100001],ans;
vector<int> tr[400001];
bool bz[100001];

ll qpower(ll a,int b) {ll ans=1;while (b) {if (b&1) ans=ans*a%mod;a=a*a%mod;b>>=1;} return ans;}
void New(int x,int y) {++len;a[len][0]=y;a[len][1]=ls[x];ls[x]=len;}

void bfs()
{
	int i,j,k,l;
	
	h=0;t=1;d[1]=root;
	while (h<t)
	{
		bz[d[++h]]=1;num[d[h]]=n-h+1;
		for (i=ls[d[h]]; i; i=a[i][1])
		if (!bz[a[i][0]])
		{
			bz[a[i][0]]=1;
			d[++t]=a[i][0];
		}
	}
}

void change(int t,int l,int r,int x,int y,int s)
{
	int mid=(l+r)/2;
	if (x<=l && r<=y) {tr[t].push_back(s);return;}
	
	if (x<=mid) change(t*2,l,mid,x,y,s);
	if (mid<y) change(t*2+1,mid+1,r,x,y,s);
}

void add(int x,int y,type s) {if (x<=y) b[++tot]={s.x,s.y,0},change(1,1,n,x,y,tot);}

void dfs(int Fa,int t,int Dis)
{
	int i,Ls=0;
	dis[t]=Dis;
	
	if (dis[Fa]) add(num[Fa],n-dis[Fa]-2,{t,Fa,0});
	if (num[Fa]==n-1) add(num[t],min(num[Fa]-1,n-3),{0,t,0}); else add(num[t],min(num[Fa]-1,n-2),{0,t,0});
	
	for (i=ls[t]; i; i=a[i][1])
	if (a[i][0]!=Fa)
	{
		dfs(t,a[i][0],(D[t]<=2)?Dis+1:1);
		if (Fa)
		{
			if (Ls) add(num[Ls],n-2,{Ls,a[i][0],0});
			Ls=a[i][0];
		}
	}
}

int gf(int t) {if (fa[t]==t) return t;return gf(fa[t]);}
void swap(int &x,int &y) {int z=x;x=y;y=z;}
void link(int id)
{
	int x=b[id].x,y=b[id].y,X=gf(x),Y=gf(y);
	if (X==Y) {b[id].X=-1; return;}
	
	if (dp[X]>dp[Y]) swap(X,Y);
	fa[X]=Y;
	if (dp[X]<dp[Y]) b[id].s=0; else b[id].s=1,++dp[Y];
	
	ans=ans*jc[size[X]]%mod*jc[size[Y]]%mod*Jc[size[X]+size[Y]]%mod;
	size[Y]+=size[X];
	b[id].X=X,b[id].Y=Y;
}
void cut(int id)
{
	int X=b[id].X,Y=b[id].Y;
	if (X==-1) return;
	
	fa[X]=X;dp[Y]-=b[id].s;
	ans=ans*jc[size[Y]]%mod*Jc[size[X]]%mod*Jc[size[Y]-size[X]]%mod;
	size[Y]-=size[X];
}

void work(int t,int l,int r)
{
	int N=tr[t].size(),i,mid=(l+r)/2;
	
	if (l==r && l==3)
	n=n;
	
	fo(i,0,N-1)
	link(tr[t][i]);
	if (l==r)
	{
		printf("%lld
",ans*jc[l]%mod);
		fd(i,N-1,0) cut(tr[t][i]);
		return;
	}
	
	work(t*2,l,mid);
	work(t*2+1,mid+1,r);
	
	fd(i,N-1,0) cut(tr[t][i]);
}

int main()
{
	freopen("circus.in","r",stdin);
	#ifdef file
	freopen("circus.out","w",stdout);
	#endif
	
	scanf("%d",&n);
	jc[0]=jc[1]=Jc[0]=Jc[1]=w[1]=1;
	fo(i,2,n) w[i]=mod-w[mod%i]*(mod/i)%mod,jc[i]=jc[i-1]*i%mod,Jc[i]=Jc[i-1]*w[i]%mod,scanf("%d%d",&j,&k),New(j,k),New(k,j),++D[j],++D[k];
	fo(i,1,n) fa[i]=i,size[i]=dp[i]=1;fa[0]=0;dp[0]=1;
	fo(i,1,n) if (D[i]>2) {root=i;break;}
	
	if (!root) {fo(i,1,n) printf("%lld
",jc[i]);return 0;}
	
	bfs();
	dfs(0,root,0);
	ans=1;
	work(1,1,n);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}
原文地址:https://www.cnblogs.com/gmh77/p/12685446.html