题解:黑箱(容斥计数)

题面描述

在小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} Cni,对于m’列,每一列都有2i -1种方案(因为不能为空,不然该列就无法被覆盖),则方案数为 C n ′ i ∗ ( 2 i − 1 ) m C_{n'}^i*(2^i-1)^m Cni(2i1)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)niCni(2i1)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)niCnjij

对于 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)niCnjij= ∑ 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)niCnjij ( 1 − 1 ) n − j = 0 (1-1)^{n-j}=0 (11)nj=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~。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值