题意:给出一个字符串s和一个整数k,s的长度是k的整数倍,把字符串从左到右每k个字符划分成一段,每一段的字符可以随意交换,最后再把这些段按原来的顺序拼接起来,要求排序后的字符串包含的块最少,块代表连续相同的字母。
思路:用dp[i][j]代表第i段以字母j结尾的最少的块的个数。确定j以后枚举第i段开头的字母k,如果和第i-1段的结尾字母不同,则dp[i][j]=min(dp[i-1][k]+cnt[i],dp[i][j]),这里cnt[i]表示第i段不同字母的个数,如果和第i-1段结尾字母相同,则dp[i][j]=min(dp[i-1][k]+cnt[i]-1,dp[i][j])。另外,由于如果第i段的开头字母和第i-1段的结尾字母是不同的话,那么第dp[i-1][k]的最小值是可以确定的,可以用一个数组来维护它,如果有相同的情况,只要减一就好了。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<map> #include<queue> #include<set> #include<stack> #include<cmath> #include<vector> #define inf 0x3f3f3f3f #define Inf 0x3FFFFFFFFFFFFFFFLL #define eps 1e-9 #define pi acos(-1.0) using namespace std; typedef long long ll; const int maxn=1000+10; char str[maxn]; int dp[maxn][26]; bool exits[maxn][26]; int cnt[maxn],minext[maxn]; int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int t; scanf("%d",&t); while(t--) { int k; scanf("%d",&k); scanf("%s",str); memset(dp,0xff,sizeof(dp)); memset(exits,0,sizeof(exits)); memset(cnt,0,sizeof(cnt)); int len=strlen(str); int seg=len/k; for(int i=0;i<len;i+=k) for(int j=i;j<i+k;++j) if(!exits[i/k+1][str[j]-'a']) { cnt[i/k+1]++; exits[i/k+1][str[j]-'a']=true; } memset(exits[0],1,sizeof(exits[0])); memset(dp[0],0,sizeof(dp[0])); minext[0]=0; for(int i=1;i<=seg;++i) { minext[i]=inf; for(int j=0;j<26;++j) if(exits[i][j]) for(int k=0;k<26;++k) if(exits[i][k]) { if(j==k&&cnt[i]!=1) continue; if(dp[i][k]==-1) dp[i][k]=minext[i-1]+cnt[i]; else dp[i][k]=min(dp[i][k],minext[i-1]+cnt[i]); if(dp[i-1][j]!=-1) dp[i][k]=min(dp[i][k],dp[i-1][j]+cnt[i]-1); minext[i]=min(minext[i],dp[i][k]); } } printf("%d\n",minext[seg]+1); } return 0; }