【luogu P4762】字符串合成 / Virus synthesis(PAM)(DP)

字符串合成 / Virus synthesis

题目链接:luogu P4762

题目大意

初始有一个空串,要你用最小花费构造给出的一个字符串。
你可以花费一个费用:
在字符串前或字符串后加一个字符,或者将这个字符串反转后得到的字符串接在这个字符串的前面或者后面。

思路

首先不难想到它反转再接其实就是构造出了一个回文串。
而且这个回文串一定是偶数长的回文串。

那回文串的一半里面又要有回文串,想到 PAM 里面的 \(trans\) 数组。
不难看到对于一个字符串,你就选一个回文串,然后两边的部分直接暴力搞,然后回文串就通过构造出一半,然后再翻转得到,那你考虑去 DP,设 \(f_i\) 为构造出 PAM 里面的第 \(i\) 个点代表的回文串的最小费用。
那对于每个回文串,如果它作为一开始选的回文串,那就会有一个答案,是 \(n-len_i+f_i\),那我们就在这些里面选一个最小的就可以了。

那接着问题就是如何 DP 得到 \(f_i\)。
考虑分奇偶讨论:

  1. 奇数

那你应该就是在里面选一个偶数的回文串,然后剩下的暴力搞,但是 PAM 只支持后缀的回文串啊。
那你就考虑分两种转移,一种是当前的最后一个位置暴力搞,然后退回求 \(f_{fa_i}\),然后加上两边暴力搞的费用 \(2\),要么是最后一个位置不暴力搞,那就是求 \(f_{fail_i}\),然后剩下的部分暴力搞。
总的来说,就是 \(f_i=\min\{f_{fa_i}+2,f_{fail_i}+len_i-len_{fail_i}\}\)。

  1. 偶数

那你就肯定是复制得到这个,很显然这个是最优的选择。
但是你不能保证你的一半是个回文串啊,你 DP 的状态都是回文串。
那你考虑像搞奇数一样搞,对于那一半,要么就是最边的位置用暴力搞,那反映在这里就是原来的串最外面的两个不要,费用就是 \(f_{fa_i}+1\)。要么就是从这里开的字符串,然后前面覆盖不到的位置暴力搞,那就是 \(f_{trans_i}+len_i/2-len_{trans_i}+1\)。(加一是翻转的费用,然后因为是求小于等于原串二分之一的最长后缀回文串,所以用的是 \(trans_i\))
总的来说,就是 \(f_i=\min\{f_{fa_i}+1,f_{trans_i}+len_i/2-len_{trans_i}+1\}\)。

然后如果长度小于等于 \(2\),那费用肯定就是原串长度,那直接特判掉就好了。
然后就好了。

代码

#include<cstdio>
#include<cstring>
#include<iostream> 

using namespace std;

struct PAM {
	int fail, num, sum, trans;
	int son[26], len, fa;
}t[100002];
int T, sn, tot, lst, f[100001], ans;
char s[100001];

int get_new(int l) {
	t[tot].len = l;
	t[tot].fail = t[tot].num = t[tot].sum = t[tot].trans = 0;
	for (int i = 0; i < 26; i++) t[tot].son[i] = 0;
	tot++;
	return tot - 1;
} 

int get_fail(int x, int pl) {
	while (s[pl - t[x].len - 1] != s[pl]) x = t[x].fail;
	return x;
}

void build_PAM() {//PAM
	tot = 0; get_new(0); get_new(0);
	t[1].fail = 0; t[0].fail = 1;
	t[0].len = 0; t[1].len = -1; lst = 0;
	for (int i = 1; i <= sn; i++) {
//		int pre = get_fail(lst, i), go = s[i] - 'a';//这个是 jzoj 的
		int pre = get_fail(lst, i), go = s[i] - 'A';//这个是 luogu 的
		if (!t[pre].son[go]) {
			int now = get_new(t[pre].len + 2);
			t[now].fail = t[get_fail(t[pre].fail, i)].son[go];
			t[pre].son[go] = now;
			t[now].fa = pre;
			t[now].sum = t[pre].sum + 1;
			if (t[now].len <= 2) t[now].trans = t[now].fail;
				else {
					int tmp = t[pre].trans;
					while (s[i - t[tmp].len - 1] != s[i] || ((t[tmp].len + 2) << 1) > t[now].len)
						tmp = t[tmp].fail;
					t[now].trans = t[tmp].son[go];
				}
		}
		lst = t[pre].son[go];
		t[lst].num++;
	}
}

void count() {
	for (int i = tot - 1; i >= 2; i--)
		t[t[i].fail].num += t[i].num;
}

void DP() {
	ans = sn;
	f[0] = 0; f[1] = 0;
	for (int i = 2; i < tot; i++) {//长度小于等于 2 的特判
		f[i] = t[i].len;
		if (t[i].len & 1 && t[i].len != 1) {//奇数长度
			f[i] = min(f[i], f[t[i].fa] + 2);
			f[i] = min(f[i], f[t[i].fail] + t[i].len - t[t[i].fail].len);
		}
		else if (!(t[i].len & 1) && t[i].len != 2) {//偶数长度
			f[i] = min(f[i], f[t[i].fa] + 1);
			f[i] = min(f[i], 1 + f[t[i].trans] + t[i].len / 2 - t[t[i].trans].len);
		}
		ans = min(ans, sn - t[i].len + f[i]);
	}
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%s", s + 1);
		sn = strlen(s + 1);
		
		build_PAM();
		DP();
		
		printf("%d\n", ans);
	}
	
	return 0;
}
上一篇:python放入列表


下一篇:使用 Python 远程登陆服务器的最佳实践