[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);
}