bzoj2958 序列染色
Description
给出一个长度为N由B、W、X三种字符组成的字符串S,你需要把每一个X染成B或W中的一个。
对于给出的K,问有多少种染色方式使得存在整数a,b,c,d使得:
1<=a<=b<c<=d<=N
Sa,Sa+1,...,Sb均为B
Sc,Sc+1,...,Sd均为W
其中b=a+K-1,d=c+K-1
由于方法可能很多,因此只需要输出最后的答案对109+7取模的结果。
Input
第一行两个正整数N,K
第二行一个长度为N的字符串S
Output
一行一个整数表示答案%(109+7)。
Sample Input
5 2
XXXXX
Sample Output
4
数据约定
对于20%的数据,N<=20
对于50%的数据,N<=2000
对于100%的数据,1<=N<=106,1<=K<=106
Solution
很容易想到N^2的做法(简单的DP)。
f[i][j][h][s]——i表示到了第i个字符,状态为j(0表示没有k个B,1表示有k个B没有k个W,2表示既有k个B又有k个W),最后一位为h(0表示B,1表示W),最后一位有连续s个。
具体转移就不说了。
其实s是不用记的(F[i][j][h]),那怎么转移状态j呢?
现有状态需要保证连续的B后有一个W,连续的W后有一个B,答案就是F[n+1][2][0],在第n+1为设为B,最后一位选B不会对答案有影响。
假如第i位是B,F[i][j][0]=F[i-1][j][1]+F[i-1][j][0];(其他同理,先不考虑j)
若第i-k+1位到第i位没有W(可以把所有的X变为B,使得有k个B),F[i][1][0]=F[i][1][0]+F[i-k][0][1];
但依照第一条原则,F[i][0][0]是胡乱转移的,而从F[i-k][0][1]转移会有已经满足中间有k个B的情况,减掉重复的即可。F[i][0][0]=F[i][0][0]-F[i-k][0][1];
PS:DP神题,容斥大法好 残忍暴力水50。
CODE
#include<cstdio>
#include<algorithm>
#define imax(a,b) ((a>b)?(a):(b))
typedef long long ll;
using namespace std;
typedef long long ll;
const ll mods=1e9+7;
const int N=1000010;
int n,m,B[N],W[N];
ll F[N][3][2];
char st[N];
int main()
{
freopen("2237.in","r",stdin);
freopen("2237.out","w",stdout);
scanf("%d%d%s",&n,&m,st+1);
st[++n]='X';
for(int i=1;i<=n;i++)
{
B[i]=B[i-1]+(st[i]=='B');
W[i]=W[i-1]+(st[i]=='W');
}
F[0][0][1]=1ll;
for(int i=1;i<=n;i++)
{
if(st[i]!='W')
for(int j=0;j<3;j++) F[i][j][0]=(F[i-1][j][1]+F[i-1][j][0])%mods;
if(st[i]!='B')
for(int j=0;j<3;j++) F[i][j][1]=(F[i-1][j][1]+F[i-1][j][0])%mods;
if(i<m) continue;
if(st[i]!='W' && W[i]==W[i-m])
{
F[i][1][0]=(F[i][1][0]+F[i-m][0][1])%mods;
F[i][0][0]=(F[i][0][0]-F[i-m][0][1])%mods;
}
if(st[i]!='B' && B[i]==B[i-m])
{
F[i][2][1]=(F[i][2][1]+F[i-m][1][0])%mods;
F[i][1][1]=(F[i][1][1]-F[i-m][1][0])%mods;
}
}
printf("%lld\n",(F[n][2][0]+mods)%mods);
return 0;
}