D. Unique Palindromes(回文串/构造)

题目

题意

给定字符串的长度,以及k个条件。
对于每个条件,给定x和c,表示原字符串的长度为x的前缀字符串,存在c个互不相同的回文子串。
原字符串由26个小写字母组成。

构造出长度为n,且满足上述k个条件的字符串。如果构造不出,输出NO;否则输出YES,并输出相应的字符串。

1<=k<=20,1<=n<=2*10^5, 3 <=x1<x2<…<xk=n, 3<=c1<=c2<=…<=ck<=10^9

比如给定以下数据,n=10,k=3,x = {4, 6, 10}, c = {4, 5, 8}

10 3
4 6 10
4 5 8

符合条件的字符串为abcbcacbab。

  • 对于前缀长度为4的子字符串abcb,存在 回文子串a,b,c,bcb,因此回文子串数量为4;
  • 对于前缀长度为6的子字符串abcbca,存在 回文子串a,b,c,bcb,cbc,因此回文子串数量为5;
  • 对于前缀长度为10的子字符串abcbcacbab,存在 回文子串a,b,c,bcb,cbc,cac,bcacb,bab,因此回文子串数量为8;

思路

规律1

长度<=3的字符串,无论字母构造,它的回文子串的个数为它长度。
以n=3为例,它的构造方式有以下几种。

aaa
aab
aba
abc

我们发现,上述任一一个字符串,回文子串的个数都为3。

规律2

对于长度>3的前缀子串(假设长度为len),它的回文子串的个数,相比长度为len-1的前缀子串的回文子串数,最多增加1。
证明:假设 长度为len的前缀子串新增的回文串,最大长度为mx,那么表示s[len-mx+1, len-mx,…,len]是回文串,假设存在另一个新增的回文串,长度为mx2 < mx,那么表示s[len-mx2+1, len-mx2,…,len]是回文串。又因为s[len-mx+1, len-mx,…,len]是回文串,可以推出s[len-mx+1, len-mx,…,len-(mx-mx2)]也是回文串。因此s[len-mx2+1, len-mx2,…,len]不是新增的回文串,矛盾。
在这里插入图片描述

推论

结合规律1和规律2,我们可以得出结论,对于长度len>3的字符串,它的回文子串的个数,不超过它的长度len本身。
因此,有以下结论

  • 如果c>x,那么显然构造不出来;
  • 以及,如果c[i]-c[i-1]>x[i]-x[i-1],也构造不出来。
构造方法

构造不出来的情况考虑好后,接下来考虑构造字符串。
主要利用以下两个结论:

  • 每新增一个新的字符,必定增加一个回文串。如abc -> abcd
  • 对于一个字符,如果重复该字符,必定增加一个回文串。如abb -> abbb

又因为k最大只取20,26个小写字母够用了。
如果未使用字符没用完,则优先使用 未使用字符;
否则使用 “重复字符”的方式,为了避免重复字符的方法,被同一个字符重复使用,导致新增回文串失败,我们用完一个字符,后边就不再使用了。
特殊情况考虑,如果需要重复的字符已经被删掉了,我们需要考虑下复用 字符已经被删除 的场景。

其他的字符,我们就用循环节 填充即可。

详见代码。
当时做题时卡住的样例,也附在代码后边了。

PS:构造题真不擅长orz

代码

#include<string>
#include<iostream>
#include<cmath>
#include<map>
using namespace std;
#define ll long long
const int maxn = 200010;

int n, k;
int c[maxn], x[maxn];
 
void solve() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= k; ++i) {
		scanf("%d", &x[i]);
		--x[i];
	}
	for (int i = 1; i <= k; ++i) {
		scanf("%d", &c[i]);
	}
	bool flag = true;
	x[0] = -1;
	c[0] = 0;
	for (int i = 1; i <= k; ++i) { // 无解的场景
		if (c[i] > x[i] + 1 || c[i] < 3 || (c[i] - c[i-1]) > (x[i] - x[i-1])) {
			flag = false;
			break;
		}
	}
	if (!flag) {
		printf("NO\n");
		return;
	}
	string ans = "";
	string res = ""; // 用于构造循环节的字符串
	char nxt = 'a';
	int pos = 0, mod;
	for (int i = 1; i <= k; ++i) {
		int tot = x[i] - x[i-1]; // 需要添加的字符总数
		int add = c[i] - c[i-1]; // 需要新增的回文字符串 数量
		int left = tot - add; // 需要新增的字符,且不 增加 回文字符串
		
		bool new_flag = false;
		if (add && nxt <= 'z') { // 是否存在还没有使用的字符 & 需要添加新增回文字符串 数量
			new_flag = true;
			pos = 0; // 重置 循环节 初始下标,防止出现 回文串
		}
		while (add && nxt <= 'z') { // 需要新增 回文 数,且还有 未使用字符
			res += nxt;
			ans += nxt;
			++nxt;
			--add;
		} 
		mod = res.size();
		// 重置循环节后, 需要防止 出现 回文串,如 ada 这种
		if (new_flag && ans.length() > 2 && ans[ans.length()-2] == res[pos]) {
			pos = (pos + 1) % mod; 
		}
		while (left--) { // 添加循环节字符,不增加 回文 数
			ans += res[pos];
			pos = (pos + 1) % mod; 
		}
		
		if (add) { // 需要添加 回文 数,且 未使用字符已经用完的场景
			int last_pos = (pos - 1 + mod) % mod; // 取上一个字符
			if (res[last_pos] == ans.back()) { // 上一个字符 等于 当前字符串的 末尾字符,则先添加,后删除
				pos = last_pos;
				while (add--) {
					ans += res[pos];
				} 
				res.erase(res.begin() + pos);
				if (pos == res.length()) { // update pos
					pos = 0;
				}
			} else { // 上一个字符 不等于 当前字符串的 末尾字符,说明该字符已经不存在于res,直接复用ans.back()
				char c = ans.back();
				while (add--) {
					ans += c;
				}
			}
			
		}
	}
	printf("YES\n%s\n", ans.c_str());
	
} 
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
}
/*
3:
aaa
aab
aba
abc

99
100 2
32 35
32 33

8 6
3 4 5 6 7 8
3 3 3 4 4 4

6 4
3 4 5 6
3 3 4 4

48 17
8 12 13 15 17 19 20 22 24 26 27 38 40 43 46 47 48
5 7 7 9 10 11 12 13 15 17 18 25 27 29 31 32 32

*/

同名GZH:对方正在debug,欢迎关注~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值