UESTC2022暑假前集训 字符串与搜索
作者:互联网
知识点:kmp,AC自动机,Manacher,后缀数组,回文自动机,搜索剪枝,迭代加深等
C-归并排序 解题报告
题目大意
对两个无序序列进行归并,求字典序最小的归并结果.
\(1\le n\le 2\times10^5,1 \le a_i,b_i\le 10^3\)。
解题思路
考虑序列a的指针p1和序列b的指针p2
如果a[p1]<b[p2],显然要选a序列
如果a[p1]=b[p2],再比较p1+1和p2+1,如果a[p1 + 1]<b[p2 + 1],显然要选a序列,否则继续比较,以此类推.
这样时间复杂度最坏达到\(O(n^2)\)
注意到值域比较小,可以使用字符串算法来解决.
考虑刚才提到的比较方式,其实就是对a[p1]的后缀和b[p2]后缀的字典序的比较
因此将a和b序列拼在一起构建后缀数组,求出每一个后缀的rank,根据rank的大小进行归并即可
时间复杂度为\(O(n)\)
代码实现
//
// Created by vv123 on 2022/5/25.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;
int a1[N], a2[N], a[N];
int sa[N], rk[N], height[N];
int c[N], t1[N], t2[N];
int plain[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
void build_sa(int n, int m) {
int *x = t1, *y = t2;
for (int i = 0; i < m; i++) c[i] = 0;
for (int i = 0; i < n; i++) c[x[i] = a[i]]++;
for (int i = 1; i < m; i++) c[i] += c[i - 1];
for (int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
for (int k = 1; k <= n; k <<= 1) {
int p = 0;
for (int i = n - k; i < n; i++) y[p++] = i;
for (int i = 0; i < n; i++) if (sa[i] >= k) y[p++] = sa[i] - k;
for (int i = 0; i < m; i++) c[i] = 0;
for (int i = 0; i < n; i++) c[x[y[i]]]++;
for (int i = 0; i < m; i++) c[i] += c[i - 1];
for (int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 0; x[sa[0]] = 0;
for (int i = 1; i < n; i++)
x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
if (p >= n) break;
m = p;
}
for (int i = 0; i < n; i++) rk[sa[i]] = i;
int k = 0;
for (int i = 0; i < n; i++) {
if (k) k--;
int j = sa[rk[i] - 1];
while (a[i + k] == a[j + k]) k++;
height[rk[i]] = k;
}
}
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a1[i];
cin >> m;
for (int i = 1; i <= m; i++)
cin >> a2[i];
for (int i = 0; i < n; i++) a[i] = a1[i + 1];
a[n] = 1001;
for (int i = n + 1; i <= n + m; i++) a[i] = a2[i - n];
a[n + m + 1] = 1001;
build_sa(n + m + 2, 1002);//char range: 0~1001
/*
for (int i = 0; i < n + m + 2; i++) cout << a[i] << " ";
cout << "\n";
for (int i = 0; i < n + m + 2; i++) cout << sa[i] << " ";
cout << "\n";
for (int i = 0; i < n + m + 2; i++) cout << rk[i] << " ";
cout << "\n";
*/
// 0...n-1 : n+1 ... n+m :
int p1 = 0, p2 = n + 1;
while (p1 <= n - 1 && p2 <= n + m) {
if (rk[p1] < rk[p2]) cout << a[p1++] << " ";
else cout << a[p2++] << " ";
}
while (p1 <= n - 1) cout << a[p1++] << " ";
while (p2 <= n + m) cout << a[p2++] << " ";
return 0;
}
G-进化 解题报告
题目大意
一开始场上有一个数seed
每次操作,选择场上的一个数\(A_0\),获得\(A_{0}\mod 7\)的分值,然后把场上清空,添加两个数
至少需要几次操作,使得分达到S
\(0\le seed \le 2^{32},1 \le S\le 300\)。
解题思路
状态空间较大,搜索树可能很深,考虑使用迭代加深搜索。
迭代加深相当于自带了最优性剪枝。
注意到每一步的最大得分为6,有可行性剪枝:
if ((maxstep - step) * 6 + sum < S) return;
易错点:
一开始场上只有一个数,不要想当然把另一个数设成0
0在这里和一个任意正整数的地位是相等的
代码实现
//
// Created by vv123 on 2022/5/28.
//
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
int seed, S;
const int mod = 4294967296;
const int p1 = 213346089, p2 = 166042049;
const int q1 = 870413, q2 = 598777;
bool ok;
char air;
int ans = 100000000;
void dfs(int a1, int a2, int step, int maxstep, int sum) {
if (step == maxstep) {
if (sum >= S) ok = true;
return;
}
if ((maxstep - step) * 6 + sum < S) return;
int a11 = (a1 * p1 + q1) % mod, a12 = (a1 * p2 + q2) % mod, sum1 = sum + (a1 % 7), a21 = (a2 * p1 + q1) % mod, a22 = (a2 * p2 + q2) % mod, sum2 = sum + (a2 % 7);
dfs(a11, a12, step + 1, maxstep, sum1);
if (step > 0 && !ok) dfs(a21, a22, step + 1, maxstep, sum2);
}
signed main() {
cin >> seed >> S;
for (int i = 1; ; i++) {
dfs(seed, 0, 0, i, 0);
if (ok) {
cout << i << "\n";
break;
}
}
return 0;
}
H-回文串 解题报告
题目大意
对于一个串 s,定义 s 的某个子串的权值为其在原串的出现次数乘以该子串的长度。
给定串 s, 求其所有回文子串的最大权值。
\(1\le|s|\le 10^6\)
解题思路
可以使用回文自动机(PAM)解决.
例如abbaabba的PAM长这样。状态应该从叶子节点往上再往下读。
1.t[i][c]
表示i号回文串在两边添加字符c以后变成的回文串的编号(类似trie)
2.len[i]
表示i号回文串的长度
3.cnt[i]
表示i号回文串在整个字符串中出现了多少次
4.fail[i]
表示节点i失配以后跳转的下一个最长后缀回文串
6.last
指向新添加一个字母后所形成的最长回文串表示的节点。
核心思想在于,每次添加一个字符c,一定由最长后缀回文串拓展出新的节点
这是因为,如果不是最长后缀回文串,而是它的子串,则PAM中已经存在本质相同的回文串
例如s = cabacaba + c, 则newnode:c-(abacaba)-c
如果当前的最长后缀是s,则期望的最长后缀变为csc
如果s前面不是c咋办呢?不停跳到下一个后缀回文串。因此需要构建fail指针。
fail的作用和kmp(AC自动机)类似,求解其实还要更容易理解一些:
while (s[n - len[x] - 1] != s[n]) x = fail[x];
考虑如何将一个字符添加到PAM
我们需要记录最新回文串的编号last,然后往回找,直到找到第一个能匹配的后缀。
如果需要新建节点q,顺便更新t[p][x]
和fail[q]
。
s[++n] = x;
int p = getfail(last);//找到第一个能匹配的后缀
if (!t[p][x]) { //如果没有出现过x(p)x
int q = newnode(len[p] + 2); //{ len[++idx] = x; return idx; },q = x(p)x
fail[q] = t[getfail(fail[p])][x];//显然q=x(p)x的失配串是x(newfail[fail[p]])x
t[p][x] = q;
}
++cnt[last = t[p][x]];
跳到空的边界情况:令0代表偶数长度回文串的根,1代表奇数长度回文串的根.令fail[0]指向1,len[1]=−1,然后令s[0]=−1(或任何一个不在原串中出现的字符)即可
注意cnt数组,构建过程中没有考虑后续串的影响,需要在最后把i的数量累加到fail[i]里面
for (int i = idx; i >= 0; i--)
cnt[fail[i]] += cnt[i];
本题只需对cnt[i] * len[i]取max即可
代码实现
//
// Created by vv123 on 2022/6/6.
//
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
struct PAM {
int n, idx, last;
int s[N], t[N][26], fail[N], cnt[N], len[N];
LL ans;
PAM() {
n = last = 0; idx = 1;
s[0] = -1; len[0] = 0; len[1] = -1; fail[0] = 1;
}
int newnode(int x) {
len[++idx] = x;
return idx;
}
int getfail(int x) {
while (s[n - len[x] - 1] != s[n]) x = fail[x];
return x;
}
void add(int x) {
s[++n] = x;
int p = getfail(last);
if (!t[p][x]) {
int q = newnode(len[p] + 2);
fail[q] = t[getfail(fail[p])][x];
t[p][x] = q;
}
++cnt[last = t[p][x]];
}
LL solve() {
LL res = 0;
for (int i = idx; i >= 0; i--) {
cnt[fail[i]] += cnt[i];
res = max(res, 1ll * cnt[i] * len[i]);
}
return res;
}
} pam;
char str[N];
int main() {
cin >> str;
for (int i = 0; str[i]; i++) {
pam.add(str[i] - 'a');
}
cout << pam.solve() << endl;
return 0;
}
Q-接头暗号 解题报告
题目大意
求暗号T在密文S中出现的次数。
\(1\le|S|,|T|\le10^6\)
解题思路
这道题是KMP算法的模板题,有幸拿到了一血,以下是讲题PPT。
纯笔记本触摸板mspaint手绘高清大图呜呜呜
代码实现
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
char p[maxn], s[maxn];
int n, m;
int ne[maxn];
int main() {
cin >> s + 1 >> p + 1;
n = strlen(p + 1), m = strlen(s + 1);
//next
for (int i = 2, j = 0; i <= n; i++) {
while (j && p[j + 1] != p[i]) j = ne[j];
if (p[j + 1] == p[i]) j++;
ne[i] = j;
}
//kmp process
int ans = 0;
for (int i = 1, j = 0; i <= m; i++) {
while (j && p[j + 1] != s[i]) j = ne[j];
if (p[j + 1] == s[i]) j++;
if (j == n) {
ans++;
j = ne[j];
}
}
cout << ans;
return 0;
}
R-国际象棋 解题报告
题目大意
给出一个5*5棋盘,'*'为空格,1和0都可以走日。求最少经过几步可以把棋盘摆成这样。如果步数大于15输出-1
11111
01111
00*11
00001
00000
\(0<t < 11\)
解题思路
容易想到使用迭代加深搜索。但会TLE on test1
考虑剪枝。可以使用A*的思路,设估价函数f(st)=g(st)+h(st),g是当前已经走过的步数,h是预计还需要走的步数。如果确保h大于等于真实值,则当f(st)大于最大深度时即可剪枝。
不难发现,每一次操作最多使一个非*点归位。我们求出当前棋盘上所有不在正确位置的点数res,res可能包含*点,因此最少还要res-1步使所有非*点归位。
这样,当step + res - 1 > maxstep时,无需继续尝试。
代码实现
//
// Created by vv123 on 2022/5/27.
//
/*
11111
01101
00111
00*01
00000
res = 3
minstep = 3
f=g+h
考虑h
每一次操作至多使一个非*点归位
因此最少还要res-1步
*/
#include <bits/stdc++.h>
using namespace std;
char s[5][5], now[5][5];
char t[5][5] = {
{'1','1','1','1','1'},
{'0','1','1','1','1'},
{'0','0','*','1','1'},
{'0','0','0','0','1'},
{'0','0','0','0','0'},
};
inline int check() {
int res = 0;
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
if (now[i][j] != t[i][j])
res++;
return res;
}
void print() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++)
cout << now[i][j];
cout << "\n";
}
cout << "\n";
}
int d[8][2] = {{1, 2}, {-1, 2}, {1, -2}, {-1, -2}, {2, 1}, {-2, 1}, {2, -1}, {-2, -1}};
bool ok;
void dfs(int step, int maxstep) {
//cout << step << " " << maxstep << "\n";
//print();
//cout << "check = " << check() << "\n";
if (step == maxstep) {
if (check() == 0) ok = true;
return;
}
if (step + check() - 1 > maxstep) {
//cout << "cut" << "\n";
return;
}
int x, y;
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
if (now[i][j] == '*') {x = i; y = j; goto loop;}
loop:;
for (int i = 0; i < 8; i++) {
int tx = x + d[i][0], ty = y + d[i][1];
if (tx < 0 || tx > 4 || ty < 0 || ty > 4) continue;
swap(now[x][y], now[tx][ty]);
dfs(step + 1, maxstep);
if (ok) return;
swap(now[x][y], now[tx][ty]);
}
}
int main() {
int T;
cin >> T;
while (T--) {
//cout << "T=" << T << endl;
char str[6];
for (int i = 0; i < 5; i++) {
cin >> str;
for (int j = 0; j < 5; j++) s[i][j] = str[j];
}
bool flag = false;
for (int i = 0; i <= 15; i++) {
memcpy(now, s, sizeof s);
ok = false;
dfs(0, i);
if (ok) {
cout << i << "\n";
flag = true;
break;
}
}
if (!flag) puts("-1");
}
//cout << -1 << endl;
return 0;
}
V-生日蛋糕 解题报告
题目大意
确定长度为\(m\)的正整数序列\(R\)和\(H\),满足
- \(V=\sum R_i^2H_i = n\)
- \(R_i > R_{i+1}, H_i>H_{i+1}\)
- \(S=R_1^2+2\sum R_iH_i\)尽可能小
输出最小的\(S\)。如果无解,输出\(0\)。
\(1\le n\le 10^4,1 \le m\le 20\)。
解题思路
考虑自底向上DFS,枚举每个位置的\(R\)和\(H\)。
首先考虑无解情况。
显然\(m\)越大,\(V\)的最小值越大,为\(\frac{m(m+1)(2m+1)}{6}\),如果大于n则一定无解。
受此启发,我们在DFS的过程中,可以求出当前状态继续DFS可能得到的\(V\)的最小值和\(S\)的最小值。前者可以用于可行性剪枝,后者可以用于最优性剪枝。
一开始加了很多乱七八糟的剪枝,仍无法避免超时。后来观察了几组大数据的路径,发现大多数时候走到最后一层得到的答案远远大于已有最优解。
考虑更强的最优性剪枝,使得依次得到的答案尽量单调减。
目前已经选了\(k\)层,剩下的\(\Delta V = n - V=\sum \limits_{k+1}^{m}R^2H\)
则剩下的$ \Delta S=\sum\limits_{k+1}^{m} 2RH = \frac{2\Delta V}{\prod\limits_{k+1}^{m} R} >\frac{2(n-V)}{R_{k+1}} $
如果\(S+\frac{2(n-V)}{R_{k+1}}\)大于已有的最小答案,显然选择\(R_{k+1}\)没有意义。
加上这条剪枝就跑的飞快了。
代码实现
//
// Created by vv123 on 2022/5/25.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 10086;
int minv[N];
int n, m, mins = 2e9;
void dfs(int k, int R, int H, int V, int S) {
//目前在从下往上数第k层,从上往下数第m-k-1层
//现在准备搭从下往上数第k+1层,从上往下数第m-k层
if (k == m) {
if (V == n) mins = min(mins, S);
return;
}
//if (V > n) return;
//if (S > mins) return;
if (S - R * R + 2 * (m - k) * (m - k) + (m - k) > mins) return;
for (int r = min(R - 1, (int)sqrt(n - V - minv[m - k - 1]) + 1); r >= m - k; r--) {
for (int h = min(H - 1, (int)((double)(n - V - minv[m - k - 1]) / r / r)); h >= m - k; h--) {
//for (int h = m - k; h < H && V + r * r * h + minv[m - k - 1] <= n; h++) {
if (V + r * r * h > n) continue;
if (S + 2 * r * h> mins) continue;
if (S + 2.0 * (n - V) / r > mins) continue;
dfs(k + 1, r, h, V + r * r * h, S + 2 * r * h);
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
minv[i] = minv[i - 1] + i * i;
for (int R1 = (int)sqrt(n - minv[m - 1]) + 1; R1 >= m; R1--) {
for (int H1 = 1; R1 * R1 * H1 + minv[m - 1] <= n; H1++) {
dfs(1, R1, H1, R1 * R1 * H1, R1 * R1 + 2 * R1 * H1);
}
}
if (mins == 2e9) mins = 0;
cout << mins << endl;
return 0;
}
W-樱之刻 解题报告
题目大意
给出n个单词s,输出它们分别在文章t中出现了几次。
\(\sum|s_i| <2\times 10 ^5, |t| < 2 \times 10^6\)
解题思路
这是AC自动机的模板题。
AC自动机可以看作trie和KMP的结合。每个节点有一个fail指针,指向下一个字符失配时该节点转移到的节点,如图所示(图源算法竞赛入门经典训练指南)
当匹配到被标记为单词结尾的节点时,说明找到了单词...吗?
考虑root->0->1, root->1->0->1->...容易发现,当文本中存在101时,会沿着第二路走到第三层节点,而这个节点不一定是单词结尾点。因此我们需要维护一个“后缀连接”last[j],表示沿着失配指针往回走时遇到的下一个单词节点编号,并在每一个匹配位置同时检查标记pid和last,另一个作用是匹配成功后沿着last更新答案。
匹配和计算fail的过程和KMP算法非常接近,只是前者将判定终点改为单词标记和last,后者按照BFS顺序递推。但在实际应用中常常优化为Trie图来使用:
int x = q.front(); q.pop();
for (int c = 0; c < SIZE; c++) {
int u = t[x][c];
if (!u) {
t[x][c] = t[f[x]][c];
continue;
}
...//求fail[u]和last[u]
}
我们在求fail时,将fail树上不存在的边f[x][c]
直接连向了t[f[f[..f[x]][c]
。
当匹配中遇到到f[x][c]
时,会直接跳转到若干次fail后成功匹配到c的节点(或者空)。
这样我们在匹配过程中就不需要跳fail的过程了。
代码实现
//
// Created by vv123 on 2022/5/30.
//
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10, MAXP = 2e5 + 10, MAXS = 2e6 + 10, SIZE = 26;
//MAXP:max sum of len(p)
struct AC {
int t[MAXP][SIZE];
int f[MAXP]; //失配指针
int pid[MAXP]; //单词结尾标志
int last[MAXP]; //fail路上下一个单词结尾的编号。对于每个i,都会尽量匹配trie树,以不同i结尾统计到的单词一定是不同位置的。
int cnt[MAXN];
int sz;
void init() {
sz = 1;
memset(t[0], 0, sizeof(t[0]));
memset(cnt, 0, sizeof(cnt));
}
void insert(char* s, int id) {
int u = 0, n = strlen(s);
for (int i = 0; i < n; i++) {
int c = s[i] - 'a';
if (!t[u][c]) {
memset(t[sz], 0, sizeof(t[sz]));
pid[sz] = 0;
t[u][c] = sz++;
}
u = t[u][c];
}
pid[u] = id;
}
void upd(int j) {
if (j) {
cnt[pid[j]]++;
upd(last[j]);
}
}
int find(char* s) {
int n = strlen(s);
int j = 0;
for (int i = 0; i < n; i++) {
int c = s[i] - 'a';
//while (j && !t[j][c]) j = f[j];
//一种优化是,我们在getfail中将t[x][c]直接连向了t[f[x](或多次)][c]。
j = t[j][c];
if (pid[j]) upd(j);
else if (last[j]) upd(last[j]);
}
}
void getfail() {
queue<int> q;
for (int c = 0; c < SIZE; c++) {
int u = t[0][c];
if (u) { f[u] = 0, q.push(u), last[u] = 0; }
}
while (!q.empty()) {
int x = q.front(); q.pop();
for (int c = 0; c < SIZE; c++) {
int u = t[x][c];
if (!u) {
t[x][c] = t[f[x]][c];
continue;
}
q.push(u);
int v = f[x];
while (v && !t[v][c]) v = f[v];
f[u] = t[v][c];
last[u] = pid[f[u]] ? f[u] : last[f[u]];
}
}
}
} ac;
char s[MAXS], p[MAXP];
int n;
map<string, int> fst;
int belong[MAXN];
int main() {
cin >> n;
ac.init();
for (int i = 1; i <= n; i++) {
cin >> p;
if (fst[p] == 0) { fst[p] = i; belong[i] = i;}
else { belong[i] = fst[p]; continue; }
ac.insert(p, i);
}
ac.getfail();
cin >> s;
ac.find(s);
for (int i = 1; i <= n; i++)
cout << ac.cnt[belong[i]] << "\n";
return 0;
}
X-APM 解题报告
题目大意
给一个字符串,求出由它的前缀和后缀(不重合,可能为空)拼成的最长回文串。
\(|s| <10^7\)
解题思路
首先用两个指针对字符串的两端暴力匹配,如果匹配完了就是整个串,否则会剩下一个中间部分。
考虑中间部分,显然只能选择最长前缀回文串或最长后缀回文串。因此我们只需要求出中间部分的最长前缀回文串,将字符串翻转后再求一遍就可以了。
Manacher算法可以再O(n)时间内求出最长回文子串,具体来说,是求出每个位置i的最长回文半径len[i]
下面总结一下Manacher算法
首先我们在每两个字符之间插入#,将原来字符串的长度n增加n+1变为2n+1,即一定是奇数长度。
(例如b#a#a#c,b#a#d#a#c)
为了方便处理,还在开头和结尾加~和!
首先考虑暴力n^2做法,枚举每一个点作为中心点,暴力向两边匹配
如何优化?设maxr为我们访问过的中心点点所在回文串的最右端的点,mid为maxr对应的中心点。
情况1:$$i \in(mid,maxr]$$
则考察\(i\)关于\(mid\)的对称点\(j\),一定有\(len[i] \ge \min(len[j],maxr-i+1)\)
因此令\(len[i] = \min(len[j],maxr-i+1)\),然后暴力拓展即可
情况2:$$i >maxr$$
暴力拓展即可
求出~#......#!的回文半径len数组后,如何求出最长前缀回文串呢?
因为处理过的字符串多了一个~占位符,所以回文串延伸到边界的条件为i - 1 == len[i]
下面考察对应原串中回文串的长度
由于经过处理后回文串都是奇数长度,故每个位置的完整长度为2 * len - 1
又因为这种处理把长度为n的回文串扩大到2n+1
故原串中回文串长度为((2 * len - 1) - 1) / 2 = len - 1
综上有
if (i - 1 == len[i]) ans = max(ans, len[i] - 1);
代码实现
//
// Created by vv123 on 2022/5/21.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 2e7 + 5e6;
char ts[N], t[N], s[N];
int len[N];
int mana(char* t) {
int cnt = 0, n = strlen(t + 1);
s[++cnt] = '~'; s[++cnt] = '#';
for (int i = 1; i <= n; i++) {
s[++cnt] = t[i];
s[++cnt] = '#';
}
s[++cnt] = '!';
//cout << cnt << " " << s + 1 << endl;
int mid = 0, maxr = 0, ans = 0;
for (int i = 2; i <= cnt - 1; i++) {
len[i] = i <= maxr ? min(len[mid * 2 - i], maxr - i + 1) : 1;
while (s[i - len[i]] == s[i + len[i]]) len[i]++;
if (i - 1 == len[i]) ans = max(ans, len[i] - 1);
if (i + len[i] > maxr) maxr = i + len[i] - 1, mid = i;
}
//for (int i = 1; i <= cnt; i++) cout << i << " ";cout << endl;
//for (int i = 1; i <= cnt; i++) cout << len[i] << " ";cout << endl;
return ans;
//alllen[i] = len[i] * 2 - 1
//reallen[i] = (alllen[i] - 1) / 2 = len[i] - 1
}
int main() {
cin >> t + 1;
int n = strlen(t + 1);
int p = 0;
while (p + 1 <= n && t[p + 1] == t[n - p]) p++;
if (p == n) {
cout << t + 1 << endl;
return 0;
}
int m = 0;
for (int i = p + 1; i <= n - p; i++) ts[++m] = t[i];
//cout << ts + 1 << endl;
int l = mana(ts);
reverse(ts + 1, ts + 1 + m);
int r = mana(ts);
//printf("%d %d %d\n", p, l, r);
cout << p * 2 + max(l, r);
return 0;
}
标签:int,++,len,UESTC2022,解题,暑假,fail,集训,回文 来源: https://www.cnblogs.com/vv123/p/16390472.html