[CSP-S模拟测试]:阴阳(容斥+计数+递推)
题目传送门(内部题16)
输入格式
第一行两个整数$n$和$m$,代表网格的大小。
接下来$n$行每行一个长度为$m$的字符串,每个字符若为$W$代表这个格子必须为阳,若为$B$代表必须为阴,若为$?$代表可以运功调整。
输出格式
一行一个整数,代表阴阳平衡的方案数模$1e9+7$的余数。
样例
样例输入1:
3 3
B?W
?B?
???
样例输出1:
8
样例输入2:
3 3
???
???
???
样例输出2:
66
数据范围与提示
对于$30\%$的数据,$n\leqslant 4,m\leqslant 4$。
对于另外$30\%$的数据,所有格子均可调整
对于$80\%$的数据,$n\leqslant 100,m\leqslant 100$。
对于$100\%$的数据,$n\leqslant 1,000,m\leqslant 1,000$。
题解
乍一看这道题似乎不可做,那么考虑如何转化问题,不妨先画两张图?
然后我们发现,合法情况要满足如下两条性质:
$\alpha.$黑色和白色的格子各自形成了两个联通块。
$\beta.$白色格子与黑色格子的分界处自上而下形成了单调不降或单调不增的序列。
那么我们考虑如何统计答案,不妨先考虑黑色位于左侧,分界线成单调不降的方案数。
首先,设$can[i][j]$表示第$i$行第$1$列到第$j$列均可被染成黑色并且第$j+1$列到第$m$列均可被染成白色,用$0/1$表示。
那么,状态转移方程即为:$dp[i][j]=[can[i][j]]\sum \limits_{k=1}{j}dp[i-1][k]$。
但是显然这个转移是$\Theta(n^3)$的,死不死撒?前缀和优化不就好了嘛~
同理,还有以下三种情况:
$\alpha.$黑色居于左侧,分界点单调不增。
$\beta.$白色居于左侧,分界点单调不降。
$\gamma.$白色居于左侧,分界点单调不增。
然后你就把答案相加了,哭哭涕涕的问我为什么不对,其实仔细想一想,会有重复的情况,比方说下面这几种:
$\alpha.$每行颜色相同,黑色位于上方:
反之同理。
$\beta.$每列颜色相同,黑色位于左侧:
反之同理。
$\gamma.$全白或全黑:
这样,你就可以开心的$AC$此题了。
时间复杂度:$\Theta(n^2)$。
期望得分:$100$分。
实际得分:$100$分。
代码时刻
#include<bits/stdc++.h>
using namespace std;
int n,m,op=0;
char ch[1001];
int can[1001][2][2];
long long dp[1001][1001][2][2],s[1001][2][2];
long long ans;
int main()
{
scanf("%d%d",&n,&m);
can[0][0][0]=0;
can[0][0][1]=m;
can[0][1][0]=1;
can[0][1][1]=m+1;
for(int i=1;i<=n;i++)
{
can[i][0][0]=0;
can[i][0][1]=m;
can[i][1][0]=1;
can[i][1][1]=m+1;
scanf("%s",ch+1);
for(int j=1;j<=m;j++)
{
if(ch[j]=='W')
{
can[i][0][0]=max(can[i][0][0],j);
can[i][1][1]=min(can[i][1][1],j);
if(op==0)op=1;
if(op==2)op=3;
}
if(ch[j]=='B')
{
can[i][0][1]=min(can[i][0][1],j-1);
can[i][1][0]=max(can[i][1][0],j+1);
if(op==0)op=2;
if(op==1)op=3;
}
}
}
switch(op)
{
case 0:ans=2;break;
case 1:ans=1;break;
case 2:ans=1;break;
}
dp[0][0][0][0]=dp[0][m][0][1]=1;
dp[0][m+1][1][0]=dp[0][1][1][1]=1;
for(int i=1;i<=n;i++)
{
s[0][0][0]=dp[i-1][0][0][0];s[0][0][1]=dp[i-1][0][0][1];
s[m+1][1][0]=dp[i-1][m+1][1][0];s[m+1][1][1]=dp[i-1][m+1][1][1];
for(int j=1;j<=m;j++)
{
s[j][0][0]=(s[j-1][0][0]+dp[i-1][j][0][0])%1000000007;
s[j][0][1]=(s[j-1][0][1]+dp[i-1][j][0][1])%1000000007;
}
for(int j=m;j>=1;j--)
{
s[j][1][0]=(s[j+1][1][0]+dp[i-1][j][1][0])%1000000007;
s[j][1][1]=(s[j+1][1][1]+dp[i-1][j][1][1])%1000000007;
}
for(int j=0;j<=m;j++)
{
if(j>=can[i][0][0]&&j<=can[i][0][1])
{
if(j>=can[i-1][0][0])
{
dp[i][j][0][0]=s[min(j,can[i-1][0][1])][0][0];
if(can[i-1][0][0]>0)dp[i][j][0][0]-=s[can[i-1][0][0]-1][0][0];
dp[i][j][0][0]=(dp[i][j][0][0]%1000000007+1000000007)%1000000007;
}
if(j<=can[i-1][0][1])
{
dp[i][j][0][1]=s[can[i-1][0][1]][0][1];
if(max(can[i-1][0][0],j)>0)dp[i][j][0][1]-=s[max(can[i-1][0][0],j)-1][0][1];
dp[i][j][0][1]=(dp[i][j][0][1]%1000000007+1000000007)%1000000007;
}
}
}
for(int j=m+1;j;j--)
{
if(j>=can[i][1][0]&&j<=can[i][1][1])
{
if(j<=can[i-1][1][1])
{
dp[i][j][1][0]=s[max(j,can[i-1][1][0])][1][0];
if(can[i-1][1][1]<=m) dp[i][j][1][0]-=s[can[i-1][1][1]+1][1][0];
dp[i][j][1][0]=(dp[i][j][1][0]%1000000007+1000000007)%1000000007;
}
if(j>=can[i-1][1][0]){
dp[i][j][1][1]=s[can[i-1][1][0]][1][1];
if(min(can[i-1][1][1],j)<=m) dp[i][j][1][1]-=s[min(can[i-1][1][1],j)+1][1][1];
dp[i][j][1][1]=(dp[i][j][1][1]%1000000007+1000000007)%1000000007;
}
}
}
}
for(int i=can[n][0][0];i<=can[n][0][1];i++)
{
ans=(ans+dp[n][i][0][0])%1000000007;
ans=(ans+dp[n][i][0][1])%1000000007;
}
for(int i=can[n][1][0];i<=can[n][1][1];i++)
{
ans=(ans+dp[n][i][1][0])%1000000007;
ans=(ans+dp[n][i][1][1])%1000000007;
}
int l1=0,r1=m,l2=1,r2=m+1;
for(int i=1;i<=n;i++)
{
l1=max(l1,can[i][0][0]);
r1=min(r1,can[i][0][1]);
l2=max(l2,can[i][1][0]);
r2=min(r2,can[i][1][1]);
}
if(l1<=r1)ans-=(r1-l1+1);
if(l2<=r2)ans-=(r2-l2+1);
int flag;
for(int i=0;i<=n;i++)
{
flag=1;
for(int j=1;j<=i;j++)
if(can[j][0][1]<m)
{flag=0;break;}
for(int j=i+1;j<=n;j++)
if(can[j][0][0]>0)
{flag=0;break;}
ans-=flag;
flag=1;
for(int j=1;j<=i;j++)
if(can[j][0][0]>0)
{flag=0;break;}
for(int j=i+1;j<=n;j++)
if(can[j][0][1]<m)
{flag=0;break;}
ans-=flag;
}
for(int i=0;i<=n;i++)
{
flag=1;
for(int j=1;j<=i;j++)
if(can[j][1][1]>1)
{flag=0;break;}
for(int j=i+1;j<=n;j++)
if(can[j][1][0]<=m)
{flag=0;break;}
ans-=flag;flag=1;
for(int j=1;j<=i;j++)
if(can[j][1][0]<=m)
{flag=0;break;}
for(int j=i+1;j<=n;j++)
if(can[j][1][1]>1)
{flag=0;break;}
ans-=flag;
}
printf("%lld",(ans%1000000007+1000000007)%1000000007);
return 0;
}
rp++