【TC SRM 548 DIV1 1000】【TC 12104】KingdomAndCities(容斥原理)(分类讨论)

传送门


题解:

注意,代码实现里面按照习惯用 k k k表示限制了度数的点数, n n n表示节点个数, m m m表示边数,以下推导过程中变量意义与上述描述一致。

很显然地,我们知道 k k k那么小,我们可以考虑对 k = 0 , 1 , 2 k=0,1,2 k=0,1,2的情况进行分类讨论。

其实也有增加单次询问复杂度来减少分类讨论的做法,这里给出一个预处理之后单次回答 O ( 1 ) O(1) O(1)的做法。

k=0

k = 0 k=0 k=0的情况我们需要计算 n n n个点, m m m条边的带标号无向连通简单图个数。

直觉告诉我们这个可以用DP来求,设 f [ n ] [ m ] f[n][m] f[n][m]表示 n n n个点, m m m条边的带标号无向连通图个数。

发现直接合并只能用背包而且复杂度有点迷,没想清楚的话还会重复计数。

考虑容斥,由于所有图的方案数显然是 ( ( n 2 ) m ) {{n\choose 2} \choose m} (m(2n)),考虑计算所有不连通的图的方案数。

根据套路,我们枚举一号点所在连通块的大小 i i i,边数 j j j,剩下的边可以在其他地方随意放置 ( ( n − i 2 ) m − j ) {{n-i\choose 2}\choose m-j} (mj(2ni)),然后分配剩下的标号 ( n − 1 i − 1 ) {n-1\choose i-1} (i1n1)

于是我们得到这样一个 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)的DP式子:

f [ n ] [ m ] = ( ( n 2 ) m ) − ∑ i = 1 n ∑ j = i − 1 min ⁡ ( m , ( i 2 ) ) f [ i ] [ j ] ⋅ ( ( n − i 2 ) m − j ) ⋅ ( n − 1 i − 1 ) f[n][m]={{n\choose2 }\choose m}-\sum_{i=1}^n\sum_{j=i-1}^{\min(m,{i\choose 2})}f[i][j]\cdot{{n-i\choose 2}\choose m-j}\cdot{n-1\choose i-1} f[n][m]=(m(2n))i=1nj=i1min(m,(2i))f[i][j](mj(2ni))(i1n1)

现在我们就解决了 k = 0 k=0 k=0的情况。这个DP数组实际上在接下来的分类讨论中也会用到。

k=1

k = 1 k=1 k=1的时候,我们先把特殊点拿出来,把没有特殊点的图建立出来,考虑一种构造方式,选择一条边,然后把这条边替换成这个度数为 2 2 2的点,连接原来边的两端。

很遗憾,这个做法显然可能是错的,万一特殊点就在某个三元环里面呢?

所以我们只需要考虑两种情况:

  1. 删去原来的边,那么在加入新点之后,比原图多了一边一点,答案为 ( m − 1 ) ⋅ f [ n − 1 ] [ m − 1 ] (m-1)\cdot f[n-1][m-1] (m1)f[n1][m1]
  2. 保留原来的边,那么在加入新点之后,比原图多了两边一点,答案为 ( m − 2 ) ⋅ f [ n − 1 ] [ m − 2 ] (m-2)\cdot f[n-1][m-2] (m2)f[n1][m2]

k=2

我们发现 k = 2 k=2 k=2又凭空多了一种情况:两个特殊点绑在一起的时候。

1.两个特殊点绑在一起

好像和 k = 1 k=1 k=1区别不是很大,这个时候我们发现有一种在 k = 1 k=1 k=1的时候不会出现的讨论:

  1. 两个端点连在同一个点上,显然这是合法的,保证图仍然是一个简单图,选择一个点,两个端点等价,方案数为 ( n − 2 ) ⋅ f [ n − 2 ] [ m − 3 ] (n-2)\cdot f[n-2][m-3] (n2)f[n2][m3]
  2. 两个端点连的点没有直接连边,方案数为 2 ( m − 2 ) ⋅ f [ n − 2 ] [ m − 2 ] 2(m-2)\cdot f[n-2][m-2] 2(m2)f[n2][m2]
  3. 两个端点连的点有直接连边,方案数为 2 ( m − 3 ) ⋅ f [ n − 2 ] [ m − 3 ] 2(m-3)\cdot f[n-2][m-3] 2(m3)f[n2][m3]

我们还需要考虑两个端点分开的情况。

  1. 两个点分别都在一个三元环中,发现两个点的连边并不冲突, ( m − 4 ) 2 ⋅ f [ n − 2 ] [ m − 4 ] (m-4)^2\cdot f[n-2][m-4] (m4)2f[n2][m4]
  2. 一个点在某个三元环中,另一个不在,两个点插入时候的选边显然不能选择同一条边, 2 ( m − 3 ) ( m − 4 ) f [ n − 2 ] [ m − 3 ] 2(m-3)(m-4)f[n-2][m-3] 2(m3)(m4)f[n2][m3]
  3. 两个点都不在任何三元环中,相当于删去了插入时候选择的边,我们发现这时候需要对它们是否选择同一条边进行讨论,如果选择了同一条边,则方案数为 ( m − 3 ) f [ n − 2 ] [ m − 3 ] (m-3)f[n-2][m-3] (m3)f[n2][m3],否则为 ( m − 2 ) ( m − 3 ) f [ n − 2 ] [ m − 2 ] (m-2)(m-3)f[n-2][m-2] (m2)(m3)f[n2][m2]

于是可以 O ( 1 ) O(1) O(1)回答每个询问了。


Update:

去看了一圈题解,发现好像我的是所有 O ( 1 ) O(1) O(1)回答询问的方法中,分类讨论总数最少的。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

cs int mod=1e9+7;
inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;}
inline int dec(int a,int b){return (a-=b)<0?a+mod:a;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
	for(;b;b>>=1,res=mul(res,a))(b&1)&&(res=mul(res,a));
	return res;
}
inline void Inc(int &a,int b){(a+=b)>=mod&&(a-=mod);}

class KingdomAndCities{
	private:
		static cs int N=55,M=N*N;
		int f[N][N],c[M][M];
		int n,m;
		inline int C(int n){return n*(n-1)>>1;}
		inline int calc(int n,int m){
			int res=0,tmp=0;
			for(int re i=1;i<=n-1;++i){
				for(int re j=i-1,lim=std::min(C(i),m);j<=lim;++j){
					Inc(tmp,mul(f[i][j],c[C(n-i)][m-j]));
				}
				Inc(res,mul(tmp,c[n-1][i-1]));
				tmp=0;
			}
			return dec(c[C(n)][m],res);
		}
		inline void init(){
			c[0][0]=1;
			for(int re i=1,lim=C(n);i<=lim;++i){
				c[i][0]=c[i][i]=1;
				for(int re j=1;j<i;++j)c[i][j]=add(c[i-1][j],c[i-1][j-1]);
			}
			for(int re i=1;i<=n;++i)
			for(int re j=i-1,lim=std::min(C(i),m);j<=lim;++j)f[i][j]=calc(i,j);
		}
		inline int g(int n,int m){return (n>0&&m>0)?f[n][m]:0;}
	public:
		int howMany(int N, int K, int M){
			n=N,m=M;
			if(n==1)return !m&&!K;
			if(n==2)return m==1&&!K;
			init();
			int res=0,tmp=0;
			switch(K){
				case 0:return f[n][m];
				case 1:
					Inc(res,mul(m-1,g(n-1,m-1)));
					Inc(res,mul(m-2,g(n-1,m-2)));
					return res;
				case 2:
					Inc(res,mul(n-2,g(n-2,m-3)));
					Inc(res,mul(2*(m-2),g(n-2,m-2)));
					Inc(res,mul(2*(m-3),g(n-2,m-3)));
					Inc(res,mul((m-4)*(m-4),g(n-2,m-4)));
					Inc(res,mul(2*(m-3)*(m-4),g(n-2,m-3)));
					Inc(res,mul(m-3,g(n-2,m-3)));
					Inc(res,mul((m-2)*(m-3),g(n-2,m-2)));
					return res;
			}
		}
};

#ifdef zxyoi

KingdomAndCities Solver;
int n,m,k;
signed main(){
	freopen("kingdom.in","r",stdin);
	std::cin>>n>>m>>k;
	std::cout<<Solver.howMany(n,m,k);
	return 0;
}

#endif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值