ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

UESTC2022暑假前集训 字符串与搜索

2022-06-19 15:04:33  阅读:116  来源: 互联网

标签:int ++ len UESTC2022 解题 暑假 fail 集训 回文


知识点: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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有