思路
线性dp。前i个分组的最小块数依赖于前i-1个分组的最小块数,以及第i-1个分组的末尾是什么字母(如果第i-1个分组的末尾与第i个分组的头部块是同一个字母,则块数会少一)
故有:d[i][j]表示第i个分组的末尾是j,前i个分组的最小块数。
转移方程:d[i][j]=min { d[i-1][k] + (如果第i组开头与第i-1组末尾字母相同,需要减一)}
解释:预先算出每个分组有几种字母,作为那个分组的块数(cost),并记录每个分组的含有的字母种类。对每个分组i,枚举末尾字母j,对d[i][j],枚举前一个分组的末尾字母k,取pre=d[i-1][k]最小——如果分组i含有字母k则pre减去1:pre=min{ pre, d[i-1][k] + if (分组 i has k): -1 }
代码
#include <bits/stdc++.h>
#define endl "\n"
#define REP(i,c,n) for(int i=(c);i<(n);++i)
#define REP1(i,c,n) for(int i=(c);i<=(n);++i)
using namespace std;
typedef long long ll;
const int maxn=1000+5;
const int MOD=0;
const int INF=0x3f3f3f3f;
int group;
char s[maxn];
int alph[maxn][26];
int d[maxn][26];
int add(int block,int pre_alpha,int now_alpha,int cost){
int rt=0;
if(block!=1 && (alph[block][pre_alpha] || (cost==1 && pre_alpha==now_alpha)))
rt=-1;
return rt;
}
void dp(){
memset(d,0x3f,sizeof(d));
/*
* 分组编号为1~group
* 故第0组是边界,置为0
*/
REP(i,0,26) d[0][i]=0;
REP1(i,1,group){
REP(j,0,26){
if(alph[i][j]){
int cost=0;
REP(h,0,26) {
if(alph[i][h]) ++cost;
}
int temp=alph[i][j];
alph[i][j]=0;
int pre=INF;
REP(h,0,26){
pre=min(pre,d[i-1][h]+add(i,h,j,cost));
}
d[i][j]=pre+cost;
alph[i][j]=temp;
}
}
}
}
// #define TEST
int main(){
#ifdef TEST
freopen("D:\\college\\clionworkplace\\test_c++2\\in.txt","r",stdin);
freopen("D:\\college\\clionworkplace\\test_c++2\\out.txt","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;
scanf("%d",&T);
while (T--){
int k;
scanf("%d%s",&k,s);
int len= strlen(s);
memset(alph,0, sizeof(alph));
int cnt=0;// 分组编号
group=len/k;// 组数
// 记录每组的字母种类
REP(i,0,len){
if(i%k==0) ++cnt;
++alph[cnt][s[i]-'a'];
}
dp();
int ans=INF;
REP(i,0,26){
ans=min(ans,d[group][i]);
}
printf("%d\n",ans);
}
return 0;
}