H. Subsequences (hard version)

这个题目好难啊,根本就不知道怎么dp,看了题解,理解了好一会才会的。

首先dp[i][j] 表示前面 i  个字符,形成长度为 j  的不同子字符串的个数。

dp[i][j]=dp[i-1][j-1]+dp[i][j-1]  这个就是说这个字符选还是不选。

但是需要注意的是,这个会有重复的字符,如果碰到重复的字符了,这样转移就会出现一点问题,这样会多加了一些情况。

比如说 xyzabca  dp[7][2] 就是在前面7个字符里面选长度为2的字符的数量,dp[7][2]=dp[6][1]+dp[6][2]

dp[6][1]转移过来,意味着这个第7个字符一定要选,所以就会有xa yx za这种答案,

但是这种答案在dp[4][2]=dp[3][1]+dp[3][2]这里转移的时候,dp[3][1]就已经包含了这种答案,所以要删去dp[3][1]

总的来说就是如果一个字符x前面已经出现过,那么就要删去以字符x结尾的该长度减1的子序列。

即 dp[i][j]-=dp[pre[i]-1][j-1]

知道这些了就可以敲代码了。 

H. Subsequences (hard version) dp_i++H. Subsequences (hard version) dp_ios_02
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
#include <cstdlib>
#include <iostream>
#define inf 0x3f3f3f3f
#define inf64 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
ll dp[110][110];
int pre[110];
char s[maxn];

int main(){
    ll n,k;
    scanf("%lld%lld",&n,&k);
    scanf("%s",s+1);
    dp[0][0]=1;
    for(int i=1;i<=n;i++){
        dp[i][0]=1;
        for(int j=1;j<=i;j++){
            dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
            if(pre[s[i]-'a']) dp[i][j]-=dp[pre[s[i]-'a']-1][j-1];
            dp[i][j]=min(dp[i][j],k);
        }
        pre[s[i]-'a']=i;
    }
    ll sum=0,ans=0;
    bool flag=0;
    for(int i=n;i>=0;i--){
        if(sum+dp[n][i]>=k){
            flag=1;
            ans+=(n-i)*1ll*(k-sum);
            break;
        }
        sum+=dp[n][i];
        ans+=(n-i)*dp[n][i];
    }
    if(flag) printf("%lld\n",ans);
    else printf("-1\n");
    return 0;
}
View Code