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\n",sum+1);//最后结果加1,因为还有一个空串 
    return 0;
}

 

猜你喜欢

转载自www.cnblogs.com/6262369sss/p/11986388.html