【dp专题】在经历了时空扭曲后的总结
2017-10-15 20:20
375 查看
在很久很久很久以后,我终于又回到了coder的舞台。
那么我们来水一发dp吧!
好懒啊不想动
题目描述
给出一个长度为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][0][0]是乱转移,中间有可能已经有B了,所以:
这样就可以了??是吧。。。
============我是分割线============
题目描述
给出一个长度为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即可
==========凑热闹的分割线===========
【题目大意】
有三个集合,分别含有a、b、c个点,要求给这些点连线,也可以全都不连,要求连接后,每两点距离为1,在同一集合的两点最短距离至少为3,问多少种不同的连接方案。
【思路】
首先我们简化问题,思考集合两两之间的关系。我们发现,若每两个集合都保证满足条件,那最后结果一定满足条件。
那么问题就变得比较简单了,两个集合间若要最短距离至少为3,那每个集合中的点只能同时与另一个集合中的一个点相连。
那么不难得出,若两个集合间需要连k条线,则可以在集合A中选k个点,在集合B中选k个点,共有k!种连接方式,即
两个集合间最少可连0条,最多可连min(A,B),因此k的范围为0~min(A,B)。
[b]这是组合数的思考方式。
然后我们还可以引申一下,用个dp来搞搞这个东西
我们发现每个点其实就是选和不选的区别,所以
就这样。
说实话也并不是很想打其他了啊www
那就到这里结束吧~~
撒花~~
那么我们来水一发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
那就到这里结束吧~~
撒花~~
相关文章推荐
- [专题总结]数位DP
- 逊哥dp专题 总结(普通dp,斜率优化dp,数位dp)
- DP专题考试总结(2)
- 数位dp初等专题小总结
- kuangbin专题十二基础dp总结
- 【专题总结】概率&期望DP
- 【DP_树形DP专题】题单总结
- DP专题考试总结(4)
- 专题(弱点)Dp训练总结【状压Dp*1+区间Dp*5+数位dp*3+树型Dp*2】【10/11】
- 【专题属性】VOJ_DP总结
- 数位DP专题总结
- dp专题总结
- 总结了一下找工作的经历~~~(1)攒rp,继续找!!
- (第Ⅱ部分 创建型模式篇) 第6章 创建型模式专题总结
- 【算法专题】工欲善其事必先利其器—— C++ STL中vector(向量/不定长数组)的常用方法总结
- .NET设计模式(7):创建型模式专题总结(Creational Pattern)
- 【专题】单调队列/斜率优化DP
- dp总结
- 四边形不等式DP基本总结
- 数位统计/数位DP 专题