CF1228E Another Filling the Grid
一、题目
二、解法
其实我们只需要关系一个数是不是 \(1\),至于 \(k\) 是拿给我们来算方案数的。
直接凭感觉容斥,钦定 \(i\) 行没有 \(1\) ,\(j\) 列没有 \(1\),\(t=(n-i)(n-j)\) 即是不被钦定格子的数量:
受这位大佬的博客的启发,上面的式子是可以通过二项式反演严格推导的,下面给出推导过程。
要什么来什么,设 \(f[i][j]\) 为 \(i\) 行没有 \(1\) ,\(j\) 行没有 \(1\) 的方案数。设 \(g[i][j]\) 是钦定 \(i\) 行没有 \(1\) ,钦定 \(j\) 行没有 \(1\) 的方案数(注意不要把 \(g[i][j]\) 理解为 \(f[i][j]\) 的二维后缀和,原理可以看我给出的二项式反演博客),那么他们满足如此的基本关系:
然后反演上面的关系式:
\(g[x][y]\) 通常是很容易求的,\(g[x][y]=C(n,x)\times(n,y)\times k^{(n-x)(n-y)}\times(k-1)^{(x+y)n-xy}\),答案就是 \(f[0][0]\),自己把 \(i=j=0\) 带进入就得到了我们一开始凭感觉写出来的柿子(这说明我们凭感觉推出来容斥可以通过二项式定理严格证明)
时间复杂度 \(O(n^2)\) 其实已经足够通过本题了,但是我们想能不能做到更好,优化的办法是 去掉内层循环,由于这里既有 \(-1\) 又有 \(C(n,j)\),所以我们可以尝试使用 二项式定理 ,但是 \(k\) 的那些项要做相应的变形。
这个变形也不是空穴来风,我们在配凑指数 \(n-j\) 和 \(j\) 以达到二项式定理中的指数定和,那么最后的柿子变成:
所以时间复杂度优化成了 \(O(n\log n)\) ,代码也极好写。
#include <cstdio>
const int M = 305;
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,k,ans,inv[M],fac[M],f1[M],f2[M];
void init()
{
inv[0]=inv[1]=fac[0]=f1[0]=f2[0]=1;
for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[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;
for(int i=1;i<=n;i++)
{
f1[i]=f1[i-1]*(k-1)%MOD;
f2[i]=f2[i-1]*k%MOD;
}
}
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;
}
int C(int n,int m)
{
if(n<m) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
signed main()
{
n=read();k=read();
init();
for(int i=0;i<=n;i++)
{
int t=(f1[i]*f2[n-i]-f1[n])%MOD;
if(i%2==0) ans=(ans+C(n,i)*qkpow(t,n))%MOD;
else ans=(ans-C(n,i)*qkpow(t,n))%MOD;
}
printf("%lld\n",(ans+MOD)%MOD);
}