题意
给定字符串的长度,以及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,欢迎关注~~