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

168 篇文章 1 订阅
91 篇文章 0 订阅
该博客主要介绍了如何利用PAM(部分匹配表)解决一个字符串构造问题,即以最小代价构造给定字符串。博主详细阐述了思路,包括如何构建回文串并利用PAM中的transtranstrans数组,以及如何通过动态规划(DP)求解奇数和偶数长度回文串的最小费用。最后,博主提供了C++代码实现。
摘要由CSDN通过智能技术生成

字符串合成 / Virus synthesis

题目链接:luogu P4762

题目大意

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

思路

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

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

那接着问题就是如何 DP 得到 f i f_i fi
考虑分奇偶讨论:

  1. 奇数

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

  1. 偶数

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

然后如果长度小于等于 2 2 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值