CodeForces - 655E dp+贪心
Posted 6262369sss
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CodeForces - 655E dp+贪心相关的知识,希望对你有一定的参考价值。
题目链接:cf崩了,so。
参考博客:https://blog.csdn.net/kyleyoung_ymj/article/details/51628238
补题。
题意:
每个样例输入两行,第一行输入两个数字n,k,第二行输入一个字符串,你可以在第二行输入的字符串后面增加n个字符(k代表字符范围,比如k=2,那么你选择的字符有a,b;如果k=4,那么你可以选择的字符有a,b,c,d,就是从字符a开始的k个字符是可选择的)。你要使输入字符串在后面增加n个指定范围的字符之后得到的字符串的子序列数目最多,输出最大的子序列数量。记得还要包括一个空串。
子序列是可以不连续的。
仅供参考,错了请指正。
这里我们假设用一个二维数组dp[i][j]表示到位置i(第i个字符)为止,以字符 j+‘a‘ 结尾的的子序列数量。
那么假设现在我们要在第i个位置上放一个字符 j ,那么我们应该更新dp[i][j]的值。
假设sum表示的是所有的dp[i-1][j](0<=j<k)的和,那么dp[i][j]=(sum+1)%mod;
这时的dp[i][j]可以分解为dp[i-1][j]+(sum-dp[i-1][j])+1;
那么在位置i增加字符 j之后,就导致以字符 j结尾的子序列增加了(sum-dp[i-1][j])+1个了。
其实就是所有不以 j 字符结尾的子序列数量加1,那为什么是增加这么多呢??
我们来一个例子,现在在字符串aabac后面增加一个字符a,在加字符a之前,我们有以下子序列
以a结尾的:a,aa,ba,aaa,aba,aaba;
以b结尾的:b,ab,aab;
以c结尾的:c,ac,bc,aac,abc,bac,aaac,aabc,abac,aabac;
好,现在我们要在后面加一个字符 ‘a‘ 了,在这之前,我们不妨给上面的所有子序列再按照另一种分类来排列一遍(加了一个空串):
1.空串,a,aa,aaa 2.b,ba 3.ab,aba 4.aab,aaba 5.c 6.ac 7.bc 8.aac 9.abc 10.bac 11.aaac 12.aabc 13.abac 14.aabac
这里的每一行的前面的字符串在增加字符‘a‘之后就变成了后面的字符串,也就是说前面的字符串是后面字符串的前缀。
也就是说除了每一行的最后一个字符串(子序列)------>>它增加字符a之后会导致以a结尾的子序列增加1,前面的所有的子序列加上字符a之后得到的子序列实际上都是已经出现过的,即已经计算在dp[i-1][j]中了,而不是因为位置 i 上增加字符a之后带来的。上面的每一行都只会增加一个新的子序列。现在我们再来看上面加粗的那四行,这四行的第一个子序列要么不是以字符a结尾,要么就是空串(第一行有"空串"两个字......),那么我们是不是可以用第一个子序列来替代整个一行子序列,也可以说是用每行的第一个子序列来代替这一行的最后一个子序列,这样我们就去掉了所有以a结尾的子序列带来的影响,而将它转化为了所有不以字符a结尾的子序列带来的影响,当然,还包括一个空串。上面有14行,在字符串aabac后面增加一个字符a之后,增加的子序列就是14个。
所以dp[i][j]=dp[i-1][j]+(sum-dp[i-1][j])+1。
计算完dp[i][j]之后,这个时候sum实际上应该更新了,因为dp[i-1][j]变成了dp[i][j]
此时 sum=(sum-dp[i-1][j]+dp[i][j])=sum-dp[i-1][j]+(sum+1)=2*sum-dp[i-1][j]+1;
在我们增加字符 j 的时候,为了使sum的值最大,我们肯定是要挑一个dp[i-1][j]的值最小的啦,这个好像叫贪心...
最后,对于已经给出的字符串,我们直接用上面的公式计算就可以了,而对于字符串后面要增加的n个字符,我们每次都找到一个字符 j ,它的dp[i-1][j]的值最小,这样根据上面的公式就可以使sum一直保持最大了,然后因为要取模,所以我们不可以直接比较大小,而是应该记录字符 ‘a‘ 到字符 ‘a ‘+k每一个字符最后出现的位置,找出现位置最小的那个字符加到字符串最后,因为位置最小的肯定是值最小的。
代码(变成一维的了):
#include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> using namespace std; typedef long long ll; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 2000005 const int mod=1e9+7; ll n,m,k,t; char str[maxn]; ll dp[26];//存以每一个字符结尾的子序列的数量 int last[26];//存每一个字符最后出现的位置 int main() { scanf("%lld%lld",&n,&k); scanf("%s",str+1); memset(last,0,sizeof(last)); int len=strlen(str+1); ll sum=0; for(int i=1;i<=len;i++){//处理输入的字符串 int id=str[i]-‘a‘; last[id]=i;//更新最后出现的位置 ll pre=dp[id];//把位置在i-1时的dp[id]暂时存一下 dp[id]=(sum+1+mod)%mod;//更新dp[id] sum=(sum-pre+dp[id]+mod)%mod;//更新sum } for(int i=1;i<=n;i++){//增加n个给定范围的字符 int Min=INF;// 找最小的位置 int id;//存位置最小的字符 for(int j=0;j<k;j++){ if(last[j]<Min){ Min=last[j]; id=j; } } last[id]=i+len;//更新最后出现大的位置 ll pre=dp[id];//把位置在i-1时的dp[id]暂时存一下 dp[id]=(sum+1+mod)%mod;//更新dp[id] sum=(sum-pre+dp[id]+mod*2)%mod;//更新sum } printf("%lld ",sum+1);//最后结果加1,因为还有一个空串 return 0; }
以上是关于CodeForces - 655E dp+贪心的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces 830A. Office Keys (背包dp+贪心) / (二分+贪心)
CodeForces - 940E - Cashback +贪心+DP
Selling Souvenirs CodeForces - 808E (分类排序后DP+贪心)
CodeForces 1005D Polycarp and Div 3(思维贪心dp)