传送门:
题目大意:给你一个字符串,可以平均分成很多段,每一段之内的元素可以任意排序,最后再按原来的顺序把每一段拼起来,问最少的块数。(块:连续相同的一段字符成为一个块)
题解:
首先我们可以发现,每一个段之内先排好序,然后如果相邻的两端有相同的元素,就可以把这两个元素分别放在尾和首,就可以减少一个块了。于是一个贪心的做法油然而生:每次对于相邻的两段,如果有相同的元素,就可以把他们放在一起,也就是答案-1.但是显然有问题,如果这一个块与上一个,这一个块与下一个是相同的公共的字符,且有且仅有一种公共的字符,就不能减两次,但是如果这一个块只有一种元素,比如:“aaaaa”,那么又可以与两端共用。
MD贼麻烦啊!
于是放弃贪心,果断DP即可,因为如果我们知道26个字母每一个的状态,就没有那么多麻烦的特判了。算一下复杂度,O(len*26*26)也是可以的。
设dp[i][j]代表第i段与第i-1段之间以某一个元素相邻时,j这一个元素可以与后面的匹配的最下块数。也就是以j作为这一段的结尾,由上一段的结尾进行转移。
统计出每一个块的不同的元素的个数num[i]。以及每一个块是否有j元素has[i][j]。然后初始化dp[0][0~25]。从1开始,每次枚举前一个段结尾的元素,如果这个元素这一个段也有,那么可以放在一起,答案可以+num[i]-1。否则只能+num[i]。
要注意的是,如果前面结尾的等于当前枚举的,是不行的,因为当前枚举的也是结尾,两个结尾不能碰到一起,除非————只有一个元素。
1 #include<queue> 2 #include<cstdio> 3 #include<vector> 4 #include<cstring> 5 #include<iostream> 6 #include<algorithm> 7 #define RG register 8 #define LL long long 9 #define fre(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout); 10 using namespace std; 11 const int MAXN=1100,INF=0x3f3f3f3f; 12 int Case,top,k,len,ans; 13 int dp[MAXN][26],num[MAXN]; 14 bool has[MAXN][26]; 15 char s[MAXN]; 16 int main() 17 { 18 scanf("%d",&Case); 19 while(Case--) 20 { 21 scanf("%d%s",&k,s); 22 len=strlen(s); 23 memset(num,0,sizeof num); 24 memset(has,0,sizeof has); 25 for(int i=0;i<len;i++) 26 { 27 if(!has[i/k][s[i]-'a']) num[i/k]++; 28 has[i/k][s[i]-'a']=1; 29 } 30 top=(len-1)/k; 31 memset(dp,0x3f3f3f3f,sizeof dp); 32 for(int i=0;i<26;i++) dp[0][i]=num[0]; 33 for(int i=1;i<=top;i++) 34 for(int j=0;j<26;j++) 35 if(has[i-1][j]) 36 for(int l=0;l<26;l++) 37 if(has[i][l]) 38 { 39 if(has[i][j] && (num[i]==1 || j!=l)) dp[i][l]=min(dp[i][l],dp[i-1][j]+num[i]-1); 40 else dp[i][l]=min(dp[i][l],dp[i-1][j]+num[i]); 41 } 42 ans=INF; 43 for(int i=0;i<26;i++) ans=min(ans,dp[top][i]); 45 printf("%d ",ans); 46 } 47 return 0; 48 }