Luogu P6277 [USACO20OPEN]Circus P

Link
特殊的,(k=n)时答案为(n!)
如果忽略标号的话,那么任意两个状态都是可以相互到达的。
因此我们考虑固定(k)个位置,计算有多少种标号排列的等价类。
由群论的基本知识可以发现,所有等价类的大小都是相同的,因此答案为(frac{k!}{size})
考虑如何计算等价类大小,一个方法是判断能否交换两个点并使得剩下的不变。
我们把所有没有分岔,两端都挂着一个连通块的链(包括端点)单独拿出来。
同一条链上的点是无法交换的,而两端挂的连通块中的点可以交换的充要条件是链长小于(n-k)
也就是说两个点可以交换的充要条件就是这两点间的路径上不存在长度不小于(n-k)的链。
如果我们把所有可以相互交换的点对连边,设连完边之后每个连通块的大小为(s_i),那么(size=prod s_i),注意这个连通块大小是要去掉链上的点的。
那么我们从大到小枚举(k),用并查集维护可以相互到达的连通块,每次把长度小于(n-k)的链的两端的连通块接起来即可。
因为连通块数和链数同阶,而一条链会被统计链长次,所有连通块的枚举次数之和为(O(n))

#include<set>
#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
const int N=100007,P=1000000007;
int n,fa[N],size[N],num[N],ifac[N],ans[N];std::vector<int>e[N];std::set<int>root;
struct edge{int u,v,w;};std::vector<edge>vec;
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
int mul(int a,int b){return 1ll*a*b%P;}
int pow(int a,int b){int r=1;for(;b;b>>=1,a=mul(a,a))if(b&1)r=mul(a,r);return r;}
int find(int x){return x==fa[x]? x:fa[x]=find(fa[x]);}
void dfs(int u,int fa,int dep,int root){for(int v:e[u])if(v^fa){if(e[v].size()==2)dfs(v,u,dep+1,root);else if(root<v)vec.push_back({root,v,dep+1});}}
int main()
{
    int n=read();
    for(int i=ans[0]=ifac[0]=1;i<=n;++i) ifac[i]=pow(ans[i]=mul(ans[i-1],i),P-2);
    for(int i=1,u,v;i<n;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
    for(int u=1;u<=n;++u) if(e[u].size()^2) num[fa[u]=u]=e[u].size(),dfs(u,0,1,u),size[u]=1,root.insert(u);
    std::sort(vec.begin(),vec.end(),[](edge a,edge b){return a.w>b.w;});
    for(int i=n-1;i;--i)
    {
	for(int u,v;vec.size()&&vec.back().w+i<n;vec.pop_back())
	    u=find(vec.back().u),v=find(vec.back().v),size[u]+=size[v]+vec.back().w-2,num[u]+=num[v]-2,root.erase(v),fa[v]=u;
	for(int x:root) ans[i]=mul(ans[i],ifac[i-n+size[x]+(n-i-1)*num[x]]);
    }
    for(int i=1;i<=n;++i) printf("%d
",ans[i]);
}
原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12730735.html