您的位置:首页 > 其它

【dp专题】在经历了时空扭曲后的总结

2017-10-15 20:20 375 查看
在很久很久很久以后,我终于又回到了coder的舞台。

那么我们来水一发dp吧!

好懒啊不想动

字符染色

【原题】BZOJ 2958

题目描述

给出一个长度为N由B、W、X三种字符组成的字符串S,你需要把每一个X染成B或W中的一个。



输入格式 2237.in

第一行两个整数N和K。

第二行一个长度为N的字符串。

输出格式 2237.out

一个整数表示答案。

输入样例 2237.in

5 2

XXXXX

输出样例 2237.out

4

数据规模:

对于20%的数据, N <= 20

对于50%的数据, N <= 2000

对于100%的数据, 1 <= N, K <= 1000000

【思路】

一看这题,显然后面的方案数是可以从前面以某种玄学的方式推过来的,那么考虑dp。

由于条件的限制性,我们若是一位一位作dp,是无法处理有连续一段长度为k这个东西的。这样一来,我们就要考虑多开一维记录是否有连续一段B/W长度为k,不难发现,需要记录的实际上只有3种情况——BW都不满足,B满足,BW都满足。

好的,接下来分析如何判断是否能满足连续一段都为B/W。

其实挺容易想到的,就是利用前缀和记录前面一共有几个B/W(用hw和hb记录吧),例如我们要判断B,那么我们只需要判断hw[i]与hw[i-k]是不是相等即可。

接下来就是转移了啊~~~

用f[i][j][k]表示到了第i位,状态是j(即0是没有连续段长为k的,1是B有W没 有,2是BW都有),k是这一位选了什么(0是B,1是W)

保证连续的B后面一定有一个W,连续的W后有一个B,答案就是f[n+1][2][0]

假设这一位选B有

f[i][j][0]=f[i-1][j][1]+f[i-1][j][0]
if(i-k+1到i没有W)
f[i][1][0]=f[i][1][0]+f[i-k][0][1];


然而这样会有重复,由于f[i][0][0]是乱转移,中间有可能已经有B了,所以:

if(i-k+1到i没有W)f[i][0][0]=f[i][0][0]-f[i-k][0][1];


这样就可以了??是吧。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int mod=1e9+7;
const int MAXN=1e6+10;
int f[MAXN][3][3];//the ith,the state,this one choice
//state 0=none,1=have k B,2=have k W
int hb[MAXN],hw[MAXN];
int n,k;
char st[MAXN];

/*void debug()
{
for(int i=1;i<=n;++i)
{
for(int j=0;j<=2;++j)
{
printf("%d ",f[i][j][0]);
}
printf("\n");
}
printf("\n");
for(int i=1;i<=n;++i)
{
for(int j=0;j<=2;++j)
{
printf("%d ",f[i][j][1]);
}
printf("\n");
}
printf("\n");
}*/

void init()
{
scanf("%d%d",&n,&k);
scanf("%s",st+1);
st[++n]='X';

for(int i=1;i<=n;++i)
{
hb[i]=hb[i-1];hw[i]=hw[i-1];
if(st[i]=='B')
hb[i]++;
if(st[i]=='W')
hw[i]++;
}
}

void solve()
{
f[0][0][1]=1;
for(int i=1;i<=n;++i)
{
if(st[i]=='B')
for(int j=0;j<3;++j)
f[i][j][0]=(f[i-1][j][0]+f[i-1][j][1])%mod;
else
if(st[i]=='W')
for(int j=0;j<3;++j)
f[i][j][1]=(f[i-1][j][0]+f[i-1][j][1])%mod;
else
for(int j=0;j<3;++j)
{
f[i][j][0]=(f[i-1][j][0]+f[i-1][j][1])%mod;
f[i][j][1]=(f[i-1][j][0]+f[i-1][j][1])%mod;
}

if(i<k)
continue;

if(st[i]=='B' || st[i]=='X')
{
if(hw[i]==hw[i-k])
{
f[i][1][0]=f[i][1][0]+f[i-k][0][1];
f[i][1][0]%=mod;
f[i][0][0]=f[i][0][0]-f[i-k][0][1]+mod;
f[i][0][0]%=mod;
}
}
if(st[i]=='W' || st[i]=='X')
{
if(hb[i]==hb[i-k])
{
f[i][2][1]=f[i][2][1]+f[i-k][1][0];
f[i][2][1]%=mod;
f[i][1][1]=f[i][1][1]-f[i-k][1][0]+mod;
f[i][1][1]%=mod;
}
}
}
//  debug();
printf("%d\n",f
[2][0]);
}

int main()
{
freopen("2237.in","r",stdin);
freopen("2237.out","w",stdout);

init();
solve();

return 0;
}


============我是分割线============

ABA字符串

【原题】BZOJ 3620

题目描述

给出一个长度为n的字符串(均为小写字母),以及一个整数k。

问该字符串的所有子串中,能被切分为A+B+A的形式且 len(A) >= k, len(B) >= 1 的子串数量有多少。

输入格式 2240.in

第一行一个字符串

第二行一个整数k

输出格式 2240.out

一个整数表示答案。

输入样例 2240.in

abcabcabc

2

输出样例 2240.out

8

数据规模:

对于30%的数据: 1 <= n <= 100

对于100%的数据: 1 <= n <= 10000, 1 <= k <= 100

n<=10000 显然是直接给你暴力水的- -

看到字符串匹配,立马就有什么AC自动机之类的东西出来。

于是我们思路清晰,用KMP来暴力

枚举子串的左端点,然后枚举右端点

对于每个子串S我们要判定是否存在一个长度在[k,(|S|-1)>>1]之间的前缀与后缀匹配

那我们就求出长度不超过(|S|-1)>>1的最长前后缀,判断是否>=k即可

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int MAXN=10010;
int nex[MAXN];
char s[MAXN];
int k,n,ans;

int main()
{
freopen("2240.in","r",stdin);
freopen("2240.out","w",stdout);

scanf("%s",&s);
scanf("%d",&k);
n=strlen(s);

ans=0;
for (int i=1;i<=n-k;i++)
{
nex[0]=nex[1]=0;
int x=0;
//      printf("0 0 ");
for (int j=2; j<=n-i+1; j++)
{
while(x && s[i-1+x]!=s[i+j-2])
x=nex[x];
if (s[i+x-1]==s[i+j-2])
x++;
nex[j]=x;
//          printf("%d ",nex[j]);
}
//      printf("\n");
int tmp=ans;
x=0;
for (int j=2;j<=n-i+1;j++)
{
while(x && s[i-1+x]!=s[i+j-2])
x=nex[x];
if (s[i-1+x]==s[i+j-2])
x++;
while((x<<1|1)>j)
x=nex[x];
if (x>=k)
ans++;
}
/*      printf("!!!%d\n",ans-tmp);
for(int j=0;j<=n;++j)
printf("%d ",nex[j]);
printf("\n");*/
}

printf("%d\n",ans);
return 0;
}


==========凑热闹的分割线===========

The Intriguing Obsession

【原题】codeforces 869C

【题目大意】

有三个集合,分别含有a、b、c个点,要求给这些点连线,也可以全都不连,要求连接后,每两点距离为1,在同一集合的两点最短距离至少为3,问多少种不同的连接方案。

【思路】

首先我们简化问题,思考集合两两之间的关系。我们发现,若每两个集合都保证满足条件,那最后结果一定满足条件。

那么问题就变得比较简单了,两个集合间若要最短距离至少为3,那每个集合中的点只能同时与另一个集合中的一个点相连。

那么不难得出,若两个集合间需要连k条线,则可以在集合A中选k个点,在集合B中选k个点,共有k!种连接方式,即

C[A][k]*C[k]*k!。


两个集合间最少可连0条,最多可连min(A,B),因此k的范围为0~min(A,B)。

[b]这是组合数的思考方式。


然后我们还可以引申一下,用个dp来搞搞这个东西

我们发现每个点其实就是选和不选的区别,所以

dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*j;


就这样。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

typedef long long LL;
const int mod=998244353;
const int MAXN=5005;
int a,b,c;
LL ans,dp[MAXN][MAXN];

void init()
{
dp[0][0]=1;
for(int i=0;i<=5000;++i)
dp[0][i]=dp[i][0]=1;

for(int i=1;i<=5000;++i)
for(int j=1;j<=5000;++j)
dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*1ll*j)%mod;
}

void solve()
{
scanf("%d%d%d",&a,&b,&c);
ans=(dp[a][b]*dp[b][c]%mod)*dp[c][a]%mod;
printf("%I64d\n",ans);
}

int main()
{
//  freopen("C.in","r",stdin);
//  freopen("C.out","w",stdout);

init();
solve();

return 0;
}


说实话也并不是很想打其他了啊www

那就到这里结束吧~~

撒花~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: