1. 程式人生 > 其它 >【luogu P4762】字串合成 / Virus synthesis(PAM)(DP)

【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;
}