[HNOI2011] 卡农

一、题目

点此看题

二、解法

身为一个正常人拿到这道题,最难解决的是数出现偶数次的限制,那我的逻辑是放数,但是发现 \(dp\) 不动。

其实是 \(dp\) 主体选错了,我们直接选集合为 \(dp\) 主体,设 \(f[i]\) 表示考虑前 \(i\) 个集合的方案数。

这就是天才的逻辑,就像科比突破重重防守,只身一人来到了篮下,他会投进去还是扣进去?

“投篮和扣篮都是两分,但在我们眼中,扣进去是六分”

这没有什么道理,因为我们是天才,这就是天才的逻辑。

那么考虑现在怎么解决放数偶数次的限制,如果前 \(i-1\) 个集合任意放数,那么集合 \(i\) 是唯一确定的,那么方案数显然是 \(A_{2^n-1}^{i-1}\),然后再考虑容斥掉那些简单的限制。

对于所有集合非空的限制,如果集合 \(i\) 为空那么前面的集合就自己放了,减去 \(f[i-1]\)

对于所有集合两两不同的限制,如果集合 \(i\) 和前面的某一个集合 \(j\) 相同,那么集合 \(j\)\(i-1\) 种取值,集合 \(i\)\(2^n-1-(i-2)=2^n-i+1\) 种取值,所以减去 \(f[i-2]\times (i-1)\times(2^n-i+1)\)

总转移方程式,因为我们是把集合当成有序,所以最后除以 \(m!\)

\[f[i]=A_{2^n-1}^{i-1}-f[i-1]-f[i-2]\times (i-1)\times(2^n-i+1) \]

三、总结

本题绝妙的一步就是解决限制的那一步,对于涉及到多个元素的限制,可以让某一些元素任意取,剩下的元素适应取来暴力满足限制,但是这可以描述所有情况,然后容斥掉其他限制即可。

#include <cstdio>
#define int long long
const int MOD = 1e8+7;
const int M = 1000005;
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,pw,fac,f[M],A[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();m=read();pw=A[0]=f[0]=fac=1;
	for(int i=1;i<=n;i++) pw=pw*2%MOD;
	for(int i=1;i<=m;i++) fac=fac*i%MOD;
	for(int i=1;i<=m;i++) A[i]=A[i-1]*(pw-i)%MOD;
	for(int i=1;i<=m;i++)
	{
		f[i]=A[i-1]-f[i-1];
		if(i>1) f[i]-=f[i-2]*(i-1)%MOD*(pw-i+1)%MOD;
		f[i]=(f[i]%MOD+MOD)%MOD;
	}
	printf("%lld\n",f[m]*qkpow(fac,MOD-2)%MOD);
}
posted @ 2021-07-29 15:31  C202044zxy  阅读(284)  评论(1编辑  收藏  举报