题目传送门(内部题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++

[CSP-S模拟测试]:阴阳(容斥+计数+递推)的更多相关文章

  1. 【BZOJ 4455】 [Zjoi2016]小星星 容斥计数

    dalao教导我们,看到计数想容斥……卡常策略:枚举顺序.除去无效状态.(树结构) #include <cstdio> #include <cstring> #include ...

  2. 数学(容斥计数):LNOI 2016 方

    Description 上帝说,不要圆,要方,于是便有了这道题.由于我们应该方,而且最好能够尽量方,所以上帝派我们来找正方形 上帝把我们派到了一个有N行M列的方格图上,图上一共有(N+1)×(M+1) ...

  3. Codeforces.997C.Sky Full of Stars(容斥 计数)

    题目链接 那场完整的Div2(Div1 ABC)在这儿.. \(Description\) 给定\(n(n\leq 10^6)\),用三种颜色染有\(n\times n\)个格子的矩形,求至少有一行或 ...

  4. 一本通 1783 矩阵填数 状压dp 容斥 计数

    LINK:矩阵填数 刚看到题目的时候感觉是无从下手的. 可以看到有n<=2的点 两个矩形. 如果只有一个矩形 矩形外的方案数容易计算考虑 矩形内的 必须要存在x这个最大值 且所有值<=x. ...

  5. 4.2 省选模拟赛 流浪者 容斥dp

    求出期望 所有情况很好搞 C(n+m-2,n-1). 也就是说求出所有情况的和乘以上面总方案的逆元即可. 可以发现所有情况和经过多少个障碍点有关 和所处位置无关. 简单的设f[i]表示从1,1到n,m ...

  6. CF1487G String Counting (容斥计数)

    传送门 考虑$c[i]>n/3$这个关键条件!最多有2个字母数量超过$n/3$! 没有奇数回文?长度大于3的回文串中间一定是长度为3的回文串,所以合法串一定没有长度=3的回文,也就是$a[i]\ ...

  7. 【洛谷】1600:天天爱跑步【LCA】【开桶】【容斥】【推式子】

    P1600 天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个 ...

  8. 【BZOJ 4818】 4818: [Sdoi2017]序列计数 (矩阵乘法、容斥计数)

    4818: [Sdoi2017]序列计数 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 560  Solved: 359 Description Al ...

  9. Co-prime HDU - 4135_容斥计数

    Code: #include<cstdio> #include<cstring> #include<cmath> #include<iostream> ...

随机推荐

  1. 编程语言-Python-GUI

    PyQt5 import sys from PyQt5 import QtWidgets,QtCore app = QtWidgets.QApplication(sys.argv) widget = ...

  2. std::map使用结构体自定义键值

    使用STL中的map时候,有时候需要使用结构题自定义键值,比如想统计点的坐标出现的次数 struct Node{ int x,y; }; ...... map<Node,int>mp; m ...

  3. 20190820 On Java8 第十章 接口

    第十章 接口 接口和抽象类提供了一种将接口与实现分离的更加结构化的方法. 抽象类和方法 包含抽象方法的类叫做抽象类.如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错. ...

  4. _groovy

    _groovy与beanshell类似,只是它执行的是apache groovy脚本,并返回结果. 如果定义了属性 “groovy.utilities”,属性将会被脚本引擎加载,这样就可以定义一些通用 ...

  5. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

  6. Day6----Python的pyinstall库的使用

    Python的pyinstaller库 pyinstaller的安装 介绍:pyinstaller是Python的第三方库,主要用于将Python代码打包成  可执行文件    ,以此达到就算没安装P ...

  7. hdu5857 Median(模拟)

    Median Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Subm ...

  8. python学习第十七天字符串的创建和操作方法

    字符串也是任何编程语言最常见的编程语言,字符串是有序的,可以通过下标来访问,可以切片,可以查找,可以替换,字符串可以和列表之间互相转换 join() split() 等函数 1,字符串的创建 单引号 ...

  9. spring 配置属性细节

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/qilixiang012/article/details/28233811 概要(红色为上一篇所讲,蓝 ...

  10. smbumount - 为普通用户卸载smb文件系统

    总览 smbumount 装载点 描述 普通用户使用这个程序可以卸载smb文件系统.它在工作时会suid到root身份,并且向普通linux用户提供了对资源更多的控制能力.在suid方面,它拥有足够的 ...