题解 网格染色
题目描述
有一个 n × m n \times m n×m的网格,可以给任意多个格子染色,问要使得每行每列都至少有一个格子被染色有多少种方案?
具体做法与心路历程
考试时也想怎么容斥,想法是先每行考虑,再每列考虑,最后在减去多了的。还是容斥题做少了,这种明显的 1 , − 1 1,-1 1,−1容斥都没做出。
具体做法
题目有两个大的限制条件:行,列。如果我们同时考虑两个不好做,那么只先考虑一个。
因为直接求方案不方便,所以我们利用补集转化: 合 法 方 案 = 总 方 案 − 非 法 方 案 合法方案=总方案-非法方案 合法方案=总方案−非法方案。
先不管行,考虑列。
那么总方案为每一列都合法的方案数,非法的方案即为列合法行不合法的方案数。
总方案数= ( 2 n − 1 ) m (2^{n}-1)^m (2n−1)m,每一列有 n n n个数,每个数可选可不选,那么这一列就有 2 n 2^{n} 2n种方案,除去全不选的一种方案再利用乘法原理求出所有方案即可。
重点在怎么求非法方案数,这个应该利用 1 , − 1 1,-1 1,−1容斥来进行
非法方案数=至少有某一行不合法的方案数-至少有某两行不合法的方案数+至少有某三行不合法的方案数…
写成式子为
∑
i
=
1
N
(
−
1
)
i
(
n
i
)
(
2
n
−
i
−
1
)
m
\sum\limits_{i=1}^N (-1)^i \begin{pmatrix} n \\ i \end{pmatrix}(2^{n-i} - 1)^m
i=1∑N(−1)i(ni)(2n−i−1)m
这样问题就解决了。
C o d e \mathcal{Code} Code
/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年10月25日 星期五 20时17分09秒
*******************************/
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
const int maxn=1e6+10;
const int mod=998244353;
long long n,m,fac[maxn],ifac[maxn],inv[maxn],f[maxn],ans;
void init()
{
fac[0]=fac[1]=ifac[0]=ifac[1]=inv[0]=inv[1]=1;
int n=1e6;
for(int i=2;i<=n;i++)
fac[i]=fac[i-1]*i%mod,inv[i]=(mod-mod/i)*inv[mod%i]%mod,ifac[i]=ifac[i-1]*inv[i]%mod;
}
long long C(int n,int m)
{
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
long long ksm(long long a,long long b)
{
long long res=1;
while(b)
{
if(b&1)
res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main()
{
//freopen("box.in","r",stdin);
//freopen("box.out","w",stdout);
cin>>n>>m;
init();
for(int i=1;i<=m;i++)
f[i]=C(m,i)*ksm(ksm(2,i)-1,n)%mod;
for(int i=m;i>=1;i--)
if((m-i+1)&1)
ans=(ans+f[i])%mod;
else
ans=(ans-f[i]+mod)%mod;
printf("%lld\n",ans);
return 0;
}