持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情
【Codeforces】Codeforces Round #570 (Div. 3) D. Messenger | 动态规划
题目链接
题目
题目大意
对于给定的长度为 的字符串 ,定义其长度为 的子序列(空串也是原字符串的一个子串)的权值为 。从原串中选择互不相同的 个子序列,输出被选出来的子序列的权值和的最小值。
定义两个原串的子序列 和 相同,需要满足以下两个条件:
思路
我们显然想要取尽可能长的子序列,于是先统计长度为 的不互相同的子序列个数。这个东西可以用动态规划求解。
首先我们预处理出 表示前 个元素中下标最大的字符 所在的位置。可以简单地用如下方式计算:最初 是 ,然后对于从 到 的每个 ,都有:
- 如果 ,
- 如果 ,
令 表示以 为右端点的长度为 的不互相同的子序列个数,因为 表示前 个元素中下标最大的字符 所在的位置,所以 表示前 个字符中以 为结尾的长度为 的子序列个数。
以 为右端点的长度为 1 的子序列个数显然为 1,然后我们试图把 放在每个长度为 的串的末尾。转移方程如下:
在统计完整个 数组之后,让我们遍历从 到 的所有可能长度 并计算长度为 的子序列数 。根据上述定义:
则我们可以取走不超过 个长度为 的串,假设我们取走了 个,对答案的贡献就为 ,我们需要选择的更短的子序列的数量为 。
如果在所有迭代之后取了的子序列数量还不够,我们将空字符串添加到答案中,并将答案增加 。如果在此之后取了的子序列数量仍然不够,则答案为 -1,否则答案为计算的总和。
代码
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=1001;
int n,m,tot;
LL k;
char ch[N];
int pre[N][26];
LL dp[N][N];
int main()
{
scanf("%d%lld ",&n,&k);
cin>>ch+1;
for (int j=0;j<26;++j)
pre[1][j]=-1;
for (int i=1;i<=n;++i)
{
for (int j=0;j<26;++j) pre[i+1][j]=pre[i][j];
pre[i+1][ch[i]-'a']=i;
}
for (int i=1;i<=n;++i) dp[i][1]=1;
for (int i=1;i<=n;++i)
{
for (int j=2;j<=i;++j)
{
for (int c=0;c<26;++c) dp[i][j]+=(pre[i][c]!=-1)*dp[pre[i][c]][j-1];
dp[i][j]=min(dp[i][j],k);
}
}
LL ans=0;
for (int len=n;len>=1;--len)
{
LL cnt=0,x;
for (int c=0;c<26;++c) cnt+=(pre[n+1][c]!=-1)*dp[pre[n+1][c]][len];
x=min(cnt,k);
ans+=x*(n-len);
k-=x;
}
if (k==1) ans+=n,k=0;
if (!k) printf("%lld",ans);
else printf("-1");
return 0;
}