您的位置:首页 > 其它

JZOJ 5485. 【清华集训2017模拟11.26】字符串

2017-12-01 11:50 471 查看

题目

一个字符串的权值是这个串包含的不同字符个数。

给定一个长度为n的字符串,把它分为k个连续非空字段,每个字符必须在某一段中,最小化字符串的权值和。

题解

肯定要DP,问题是怎么设状态。

这个状态需要既表示了位置,又表示了块数,还表示了当前的答案

设法①,设f[i][j]表示做到i,切了j次的答案。这样子顶多撑得下n≤200.

转移显然。(然而斜率优化可以过n≤1500)

还有一种方法,不考虑位置的严格转移(即i推向i+1),但是状态存的是字符串的位置。

设法②,可以设f[i][j]表示目前分割了i块,答案为i+j的最右边的位置。

这个状态既表示了位置,又表示了块数,还表示了当前的答案

若答案为i+j,说明附加代价为j-1(“附加代价”表示因为字母不同而使答案增加的量)。

那么在转移的时候,由于不允许严格按照相邻位置来转移,那么就可以考虑按照下一段的不同字母个数转移。

设g[i][j]表示从第i位始,假设此段的结束点为l,那么s[i~l]中不同字母个数为j的最右边的位置。

这个怎么预处理?

可以发现,g[i][j]≤g[i+1][j],那么直接暴力就好了。

然而这样肯定有缺点。(跑起来慢死了)

可以先枚举j,枚举左端点i,右端点r可以直接算。复杂度O(25n)

fo(j,1,25)
{
r=0;
fo(i,1,n)
{
while (r<n && (temp[s[r+1]]||cnt<j))
{
if (temp[s[r+1]]==0) cnt++;//拿个桶标记一下这个字母有没出现过就好了
temp[s[r+1]]++;
r++;
}
g[i][j]=r;temp[s[i]]--;
if (temp[s[i]]==0) cnt--;
}
}


设下一段不同字母个数为k,那么附加代价就加上k-1。

转移:f[i+1][j+k−1]=max(f[i+1][j+k−1],g[f[i][j]+1][k])

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int n,k,K,i,j,m,s,l,x,ans,cnt;
int val[N],d[N][26];
int f[N][30],g[N][30];
int _2[30];
char st
;
int getcount(int l,int r){
int i,res=0;
fo(i,0,25)if(d[r][i]-d[l-1][i])res++;
return res;
}
int main(){
_2[1]=1;fo(i,2,29)_2[i]=_2[i-1]*2;
scanf("%d%d\n",&n,&K);
scanf("%s\n",st+1);
fo(i,1,n){
x=st[i]-'a';
val[i]=x;
fo(j,0,25)d[i][j]=d[i-1][j];
d[i][x]++;
}
fo(i,0,26)g
[i]=g[n+1][i]=n;
fd(i,n-1,0){
fo(j,0,26){
g[i][j]=g[i+1][j];
while(g[i][j]>i && getcount(i,g[i][j])>j)g[i][j]--;
}
}
f[0][0]=0;
fo(i,0,K-1)
fo(j,0,25)
fo(k,1,26){
if(j+k>25)break;
f[i+1][j+k-1]=max(g[f[i][j]+1][k],f[i+1][j+k-1]);
}
fo(i,0,26)
if(f[K][i]==n){
printf("%d",K+i);
break;
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: