题面描述
在小yy面前摆着一个大箱子,箱子被划分成了 个格子,箱子的侧面都是透明的,而且箱子很高,所以小yy只能从侧面观察箱子。
现在,箱子中有些格子被不透明的物体塞满了,由于小yy只能从侧面看,所以他只知道有哪些行和列有不透明的格子,求有多少种方案满足行和列不透明的情况满足小yy所看到的实际情况,对998244353取模。
小yy对于OI题造诣不深,所以被这道题难住了,但是身为男主的他不能输在新手村。所以你能帮他解决这个问题吗?(不能,咕咕 )
输入格式
输入第一行,两个整数n,m。
接下来两行,每行一个仅由’.’和’B’组成的字符串,长度分别为n和m。
其中’.’表示当前行或列透明,’B’表示当前行或列存在不透明的格子。
输出格式
输出仅一行,表示最终方案数对998244353取模的结果。
一个显然的想法,因为一阵行或列看不见的时候情况为1,不需要考虑,我们可以试着删除这个行或者列。具体来说我们统计有n’行是有非透明的,m’列是有非透明的,那么问题就转化成了n’每行以及m’每列都至少有一个不透明的格子的方案数。
考虑容斥:枚举至多覆盖了i行但是至少覆盖了1行(需要保证列一定是全部覆盖的,所以至少为1行)的方案数。假设是i行,那么选择的方案数就是
C
n
′
i
C_{n'}^{i}
Cn′i,对于m’列,每一列都有2i -1种方案(因为不能为空,不然该列就无法被覆盖),则方案数为
C
n
′
i
∗
(
2
i
−
1
)
m
C_{n'}^i*(2^i-1)^m
Cn′i∗(2i−1)m。
具体式子应该是 ∑ i = n 1 ( − 1 ) n − i C n i ∗ ( 2 i − 1 ) m \sum_{i=n}^{1}(-1)^{n-i}C_{n}^i*(2^i-1)^m ∑i=n1(−1)n−iCni∗(2i−1)m。
思考一下为什么这个式子是正确的?为什么要这样容斥呢?为什么不能直接是至多覆盖了n’行的方案数直接减去覆盖了n’-1行的方案数呢?(我订正题目产生的困惑)
经过一番缜密思考后,我们不妨这样考虑,考虑每一种情况最终被计算了多少次。显然我们需要让1~n’-1行被覆盖的方案数的贡献统计为0,而n’行被覆盖的方案数的贡献为1(即最终答案)。
考虑当选择至多i行时,只有j(i>=j)行被覆盖的贡献一共被计算了几次。
把这一个j行看成一个整体,剩下就会有n-j个空挡,还需要选择i-j行(这i-j行选择了但是不会有格子,故会发生重复),所以j行被覆盖的方案数一共会被重复算上 ( − 1 ) n − i C n − j i − j (-1)^{n-i}C_{n-j}^{i-j} (−1)n−iCn−ji−j
对于 j < n ′ j<n' j<n′,一共会被计算 ∑ i = n j ( − 1 ) n − i C n − j i − j = \sum_{i=n}^j(-1)^{n-i}C_{n-j}^{i-j}= ∑i=nj(−1)n−iCn−ji−j= ∑ i = n 1 ( − 1 ) n − i C n − j i − j \sum_{i=n}^{1}(-1)^{n-i}C_{n-j}^{i-j} ∑i=n1(−1)n−iCn−ji−j ( 1 − 1 ) n − j = 0 (1-1)^{n-j}=0 (1−1)n−j=0(简单的二项式展开,C的系数为负数为0,所以i范围可以由>=j变成>=1即可)
只有当 j = n ′ j=n' j=n′时才会被计算上一次,所以这样容斥是正确的。
感觉自己无意中证明了 0 0 = 1 0^0=1 00=1(大雾)
wc妙啊
代码:
#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e6+7,mod=998244353;
inline int read()
{
int s=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') f=(ch=='-'?-1:f),ch=getchar();
while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*f;
}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int add(int a,int b){return (ll)a+b>=mod?a+b-mod:a+b;}
int pow2(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
ll ans=0;
int n,m,A=0,B=0,fac[N],inv[N];
char a[N],b[N];
int C(int a,int b)
{
if(a<b) return 0;
return mul(fac[a],mul(inv[b],inv[a-b]));
}
int cnt(int x)
{
if(x&1) return -1;
return 1;
}
int main()
{
freopen("box.in","r",stdin);
freopen("box.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
{
a[i]=getchar();
if(a[i]=='B') A++;
}
a[0]=getchar();
for(int i=1;i<=m;i++)
{
b[i]=getchar();
if(b[i]=='B') B++;
}
fac[0]=1;
for(int i=1;i<=max(n,m);i++)
fac[i]=mul(fac[i-1],i);
inv[max(n,m)]=pow2(fac[max(n,m)],mod-2);
for(int i=max(n,m)-1;i>=0;i--)
inv[i]=mul(inv[i+1],i+1);
for(int i=A;i>=1;i--)
ans=(ans+(ll)cnt(A-i)*C(A,i)*pow2((pow2(2,i)-1+mod)%mod,B)%mod+mod)%mod;
// for(int i=0;i<=A;i++)
// ans=(ans+(ll)cnt(i)*C(A,i)*pow2((pow2(2,A-i)-1+mod)%mod,B)%mod+mod)%mod;
printf("%d\n",ans);
return 0;
}
如果耐心看过代码的朋友,应该会发现注释代码,令人惊奇的是这也可以AC。我又认真想了一下,发现这个注释代码状态相反,应该至少有i行没有被覆盖的状态,至于贡献的统计应该是类似的,所以也是正确的。
通过这一道题,感觉对容斥的理解更加深刻了。That’s good~。