A common way to uniquely encode a string is by replacing its consecutive repeating characters (or
“chunks”) by the number of times the character occurs followed by the character itself. For example,
the string “aabbbaabaaaa” may be encoded as “2a3b2a1b4a”. (Note for this problem even a single
character “b” is replaced by “1b”.)
Suppose we have a string S and a number k such that k divides the length of S. Let S1 be the
substring of S from 1 to k, S2 be the substring of S from k + 1 to 2k, and so on. We wish to rearrange
the characters of each block Si
independently so that the concatenation of those permutations S
′ has
as few chunks of the same character as possible. Output the fewest number of chunks.
For example, let S be “uuvuwwuv” and k be 4. Then S1 is “uuvu” and has three chunks, but may
be rearranged to “uuuv” which has two chunks. Similarly, S2 may be rearranged to “vuww”. Then S
′
,
or S1S2, is “uuuvvuww” which is 4 chunks, indeed the minimum number of chunks.
Input
The input begins with a line containing t (1 ≤ t ≤ 100), the number of test cases. The following t lines
contain an integer k and a string S made of no more than 1000 lowercase English alphabet letters. It
is guaranteed that k will divide the length of S.
Output
For each test case, output a single line containing the minimum number of chunks after we rearrange
S as described above.
Sample Input
2
5 helloworld
7 thefewestflops
Sample Output
8
10
题意:给定K和长度为len的字符串,len是K的倍数,字符串可以划分为len/K段。每段如果相邻字母相同,则视为一个cent,如果相邻两段的首尾字母也相同,那他们也可以认为是一个cent,每段的字母可以随意重新排列,问len的字符串最小cent是多少。
思路:DP[i][j]代表前i段以第i段中的第j个字母结尾的最小cent数量。
状态转移方程DP[i][j]=min(DP[i-1][l]+centi-1) 首尾可以合并
DP[i][j]=min(DP[i-1][l]+cent)首尾不可合并。
l为枚举的第i-1段第l个字母结尾。
如何判断两个字段首尾字母是否可以合并?
如果i-1段的第l个字母在i段出现过,且i段最后一个字母不是它或者i段只有一个cent,则由于每段可以自由排列,我们只确定了每段的最后一个字母,所以完全可以判定合并了两段的首尾。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
//序列划分
const int maxn=1005;
const int INF=0x3f3f3f3f;
int DP[maxn][maxn];//第i块以第i块中的第j位结尾的最小值
bool vis[30];//标记字母i出现过
char str[maxn];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int K;
memset(str,'\0',sizeof(str));
scanf("%d%s",&K,str+1);
int len=strlen(str+1);
int cent=0;//一块中出现的字母种类数
memset(vis,false,sizeof(vis));
memset(DP,0x3f,sizeof(DP));
for(int i=1; i<=K; i++)
vis[str[i]-'a']=true;
for(int i=0; i<='z'-'a'; i++)
if(vis[i]==true)
cent++;
for(int j=1; j<=K; j++)
DP[1][j]=cent;
//上面处理第一块,下面处理剩下的块
for(int i=2; i*K<=len; i++) //枚举块数
{
memset(vis,false,sizeof(vis));
cent=0;
for(int j=1; j<=K; j++) //枚举第i块的第j位
vis[str[(i-1)*K+j]-'a']=true;
for(int j=0; j<='z'-'a'; j++)
if(vis[j]==true)
cent++;
for(int j=1; j<=K; j++) //枚举第i块的第j位
{
for(int l=1; l<=K; l++) //枚举第i-1块的第l位
{
//i-1段的某字母在i段出现过&&(i段只有这一种字母||i段最后一个字母不是此字母)
//此字母就可以作为i-1段末尾i段开始,从而缩减一次结果
if(vis[str[(i-2)*K+l]-'a']==true&&(cent==1||str[(i-2)*K+l]!=str[(i-1)*K+j]))
{
DP[i][j]=min(DP[i][j],DP[i-1][l]+cent-1);
}
else
{
DP[i][j]=min(DP[i][j],DP[i-1][l]+cent);
}
}
}
}
int result=INF;
for(int j=1;j<=K;j++)
result=min(result,DP[len/K][j]);
printf("%d\n",result);
}
return 0;
}