Description
文本压缩的算法有很多种,这里给出一种叫做PermRLE的压缩算法、
定义一个整数k, PermRLE算法依赖于一种压缩顺序。所谓的压缩顺序就是一种1~k的排列。例如当k=4的时候,其中一种排列方式是{1,2,4,3},对于字符串“abdb”,按照这种排列方式进行排列之后就变成了“abbd”。
对于一段长度为Len的文本,其中k能整除Len,那么PermRLE算法就是把整个文本分成Len div k段,然后每一段按照一种1~k的排列方式进行重新排列,重新排列完之后,就把这Len div k段进行合并。对于合并之后得到一个新字符串,PermRLE算法就是把字符串中连续相同的字符合并成一个字符,例如aabccaabb合并后就变成了abcab。
给出一段长度为len(1<=len<=50000)的文本以及一个整数k(1<=k<=16)其中k能整除Len,你的任务就是找出一种1~k的排列,使得PermRLE算法压缩之后的文本的长度最小。当然,为了降低难度,你只需输出文本压缩之后最小的长度,而不需要输出这种排列。
Input
输入第一行有一个整数k(1<=k<=16),意义如上所述。
接下来一行有一个字符串,保证字符串的长度能被k整除,且字符串里仅含有小写字母。
Output
输出一行,仅包含文本压缩之后的最小长度。
Sample Input
输入1:
4
abcabcabcabc
输入2:
3
abcabcabcabc
Sample out
输出1:
7
输出2:
12
Data Constraint
对于50%的数据,1<=k<=5,1<=len<=1000
对于100%的数据,1<=k<=16,1<=len<=50000
Solution
根据题意,我们是要选择一个关于K的排列,使得连续重复的字符最多。K很小,又是关于排列,于是我们可以用状压DP解决。
设
FS,i
F
S
,
i
表示当前K的排列中使用过的数是那些,
i
i
表示最后一个放入中的是那个数,最多能够删除多少字符。
那么此时,我们就能够表示出关于K的排列,我们只需要知道转移的代价是什么就好了。这个东西我们可以预处理出来
设
Blocki,j
B
l
o
c
k
i
,
j
表示在
N/K
N
/
K
个块中
i
i
位置和的字符相同的块有多少,那么转移方程就可以列出来:
现在还要处理的就是块与块之间交接的重复字符,但我们就要知道第一个放入的K的排列中的数是什么。我们首先预处理一个 Nexi,j N e x i , j 表示每个块与它下一个块第 i i 个字符和第个字符有多少个相同的,那么我们就可以做K次状压DP,每一次选取一个开头,从它开始转移。那么
最终的输出答案就是 N−Ans N − A n s ,时间复杂度为 O(NK+K32K) O ( N K + K 3 2 K )
Code
# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 5e4 + 5;
const int K = (1 << 16) + 5;
char s[N];
int f[K][20],nx[20][20],block[20][20];
int ans,k,len;
int main()
{
scanf("%d%s",&k,s + 1); len = strlen(s + 1);
for (int i = 0 ; i < len / k ; ++i)
for (int j = 1 ; j <= k ; ++j)
for (int l = 1 ; l <= k ; ++l)
{
block[j][l] += s[i * k + j] == s[i * k + l];
if (k * (i + 1) + l <= len) nx[j][l] += s[i * k + j] == s[k * (i + 1) + l];
}
int lim = (1 << k) - 1;
for (int s = 1 ; s <= k ; ++s)
{
memset(f,-0x3f,sizeof(f)); f[1 << (s - 1)][s] = 0;
for (int i = 0 ; i <= lim ; ++i)
{
for (int j = 1 ; j <= k ; ++j)
if (i & (1 << (j - 1)))
for (int l = 1 ; l <= k ; ++l)
if (i & (1 << (l - 1)) && j != l)
f[i][j] = max(f[i][j],f[i ^ (1 << (j - 1))][l] + block[j][l]);
}
for (int i = 1 ; i <= k ; ++i)
for (int j = 1 ; j <= k ; ++j)
if (i != j) ans = max(ans,f[lim][j] + nx[j][s]);
}
printf("%d\n",len - ans);
return 0;
}