• [USACO 2020 US Open Platinum] Circus


    一、题目

    点此看题

    二、解法

    考虑把所有元素集中在 \(1,2...k\) 中,我们称之为本源状态,我们希望找到从一个本源状态出发可以到达其他的本源状态数。考虑对于两个元素 \((x,y)\),如果可以在其他元素保持不变的情况下交换它们,就把他们划分在一个等价类中,那么一个等价类中的元素可以任意交换,所以要除去它们内部的顺序,设等价类的大小为 \(s_i\),则答案是:

    \[\frac{k!}{\prod s_i!} \]

    寻找 \((x,y)\) 能够交换的必要条件,考虑树上的一条极长的二度链,设二度链的长度为 \(l\),它的端点分别是 \(u,v\),端点 \(u,v\) 对应的子树大小分别是 \(A,B\)

    二度路径

    \(k\geq n-l-1\) 时,\(k\geq (A-1)+(B-1)\),我们把其他点都填满在 \(u,v\) 的子树中(不含 \(u,v\) 两端点),那么链上会有 \(k-(n-l-1)\) 个点,并且这些点的相对顺序是不会改变的(特别地 \(k=n-l-1\) 也成立)

    所以我们可以得到必要条件:对于任意满足 \(k\geq n-l-1\) 的二度链,\((x,y)\) 必须同在 \(u\) 或者 \(v\) 的子树内(不含 \(u,v\) 两端点),充分性证明需要构造和讨论,可以参考 yhx的博客,有图解应该是比较好懂的。


    为了计算答案,我们可以把满足 \(l\geq n-k-1\) 的极长二度链断开,然后原图会形成若干个连通块。

    考虑第 \(i\) 个连通块的大小是 \(|C_i|\),我们计算可以到达它的点数。考虑以这个连通块为根,统计每一个子树的贡献(这里是相对原树而言的,不考虑断边),设其中一个子树大小为 \(sz\),子树总个数为 \(y\),那么我们肯定会把元素堆在剩下 \(n-sz-1\) 个点中(注意也不能放在点 \(u\),所以要减 \(1\)),所以不能到达它的点数就是 \(k-(n-sz-1)\),那么不能到达它的点数总和就是:

    \[\sum_{i=1}^{y} [k-(n-sz_i-1)]=(k-n+1)\cdot y+(n-|C_i|) \]

    所以可以到达它的点数总和就是 \(k-[(k-n+1)\cdot y+(n-|C_i|)]=(n-k-1)\cdot (y-1)+|C_i|-1\)

    考虑从小到大枚举 \(n-k-1\),然后连接 \(l<n-k-1\) 的二度链,用并查集维护联通块的大小和度数(即上文中的子树个数),计算答案时可以暴力枚举所有连通块,因为连通块个数等于还未加入的二度链数\(+1\),那么一个二度链只会贡献其长度次的查询,所以暴力的次数不超过 \(O(n)\),时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <set>
    using namespace std;
    const int M = 100005;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,fac[M],inv[M],key[M],deg[M],dep[M];
    int rt,p[M],fa[M],siz[M],ans[M];
    vector<int> g[M];set<int> s;
    struct node{int l,u,v;}a[M];
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    }
    void dfs(int u)
    {
    	for(int v:g[u]) if(v^p[u])
    		p[v]=u,dep[v]=dep[u]+1,dfs(v);
    }
    int find(int x)
    {
    	if(x!=fa[x]) fa[x]=find(fa[x]);
    	return fa[x];
    }
    void merge(int u,int v,int c)
    {
    	int x=find(u),y=find(v);
    	fa[x]=y;siz[y]+=siz[x]+c-1;
    	deg[y]+=deg[x]-2;s.erase(x);
    }
    signed main()
    {
    	n=read();init(n);
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);deg[u]++;
    		g[v].push_back(u);deg[v]++;
    	}
    	for(int i=1;i<=n;i++) if(deg[i]!=2)
    		key[i]=1,s.insert(rt=i);
    	dfs(rt);
    	for(int i=1;i<=n;i++) if(key[i] && i!=rt)
    	{
    		int v=0;
    		for(v=p[i];!key[v];v=p[v]);
    		a[++m]=node{dep[i]-dep[v],v,i};
    	}
    	sort(a+1,a+1+m,[&](node a,node b)
    	{return a.l<b.l;});
    	for(int i=1;i<=n;i++)
    		fa[i]=i,siz[i]=1,ans[i]=fac[i];
    	for(int i=0,j=1;i<n-1;i++)
    	{
    		while(j<=m && a[j].l<i)
    			merge(a[j].u,a[j].v,a[j].l),j++;
    		int &t=ans[n-i-1];
    		for(int x:s)
    			t=t*inv[i*(deg[x]-1)+siz[x]-1]%MOD;
    	}
    	for(int i=1;i<=n;i++)
    		printf("%lld\n",ans[i]);
    }
    
  • 相关阅读:
    GDI绘制时钟效果,与系统时间保持同步,基于Winform
    Asp.Net Core API网关Ocelot
    Docker打包 Asp.Net Core应用,在CentOS上运行
    【C#】数据库脚本生成工具(二)
    【C#附源码】数据库文档生成工具支持(Excel+Htm)
    微信小程序初使心得【微信小程序快速入门】
    论:开发者信仰之“天下IT是一家“(Java .NET篇)
    线程池,千万注意,原来很多人都在错用
    .NET跨平台之运行与Linux上的Jexus服务器
    StackExchange.Redis 之 SortedSet 类型示例
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16361526.html
Copyright © 2020-2023  润新知