2023“钉耙编程”中国大学生算法设计超级联赛(5)补题向题解(1003)

1003 String Magic (Easy Version)

知识点 manacher + 主席树/二维数点

题意

给定长度为 n n n 的字符串,询问其中满足以下要求的回文串的个数

  1. 偶数长度
  2. 左右一半也都是回文串

思路

用 manacher 算法预处理后对于任意以 # 为中心的回文串都是原串的一个偶回文, 所以对于当 s i = s_i = si= # 所有以 i i i 为中心的回文子串都满足条件1, 考虑满足条件2的有多少个。

设以 i i i 为中心的最大回文串的左右端点为 [ l = i − p i + 1 , r = i + p i − 1 ] [l = i - p_i + 1, r = i + p_i - 1] [l=ipi+1,r=i+pi1] 设存在以 j j j 为中心,回文半径为 k k k 的回文串满足 l < j < i l < j < i l<j<i j + k − 1 = i j + k - 1 = i j+k1=i j − k + 1 > = l j - k + 1 >= l jk+1>=l
这样的回文串就是题目所询问的左边那一半的回文,因为回文对称,所以我们只需要询问左边这样的个数,右边自然会有对应符合要求的。

但由于 manacher 算法 p i p_i pi 维护的以 i i i 为中心的回文串的最大回文半径这样的回文串可能是 j + p j − 1 > i j + p_j - 1 > i j+pj1>i 但他们的子串是符合的,所以只要统计 j + p j − 1 > = i j + p_j - 1 >= i j+pj1>=i 的即可。注意这样的回文串的左边界不能超过 l l l, (i - j <= j - l)
用主席树维护权值树下标为 i + p i − 1 i + p_i - 1 i+pi1,每次查询 [ j , i − 1 ] [j, i - 1] [j,i1] 区间中值大于等于当前 i i i 的即可,因为算上 # manacher 维护的都是奇数长度的回文串 所以 j = ( i + ( i − p [ i ] + 1 ) ) / 2 j = (i + (i - p[i] + 1)) / 2 j=(i+(ip[i]+1))/2

主席树维护代码

#include 
using namespace std;

#define ll long long
const int N = 2e5 + 10;

string a;
char s[N];
int n, p[N];

void manacher(){
    int k = 0;
    s[++ k] = '$'; s[++ k] = '#'; // 开始符号
    for(int i = 1; i <= n; i ++){
        s[++ k] = a[i - 1]; s[++ k] = '#';
    }
    s[++ k] = '^'; // 截止符防止越界
    n = k;

    int mid = 0, max_r = 0; // mid:当前出现过的回文串中右边界最大的回文中心  max_r:出现过的回文最大右边界(开区间)
    for(int i = 1; i <= n; i ++){
        if(i < max_r) p[i] = min(p[mid * 2 - i], max_r - i); 
        else p[i] = 1;
        while(s[i - p[i]] == s[i + p[i]]) p[i] ++;
        if(i + p[i] > max_r){
            mid = i;
            max_r = i + p[i];
        }
    }
}

struct node{ // 维护的是多版本的权值树
    int ls, rs, sum;
}tr[N * 40];

int T[N], tot;

void build(int &now, int pre, int loc, int l, int r){
    now = ++ tot;
    tr[now] = tr[pre];
    tr[now].sum ++;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    if(loc <= mid) build(tr[now].ls, tr[pre].ls, loc, l, mid);
    else build(tr[now].rs, tr[pre].rs, loc, mid + 1, r);
}

int query(int lth, int rth, int ql, int qr, int l, int r){ // 询问版本 lth ~ rth 之间 区间 [ql, qr] 的和
    if(l >= ql && r <= qr) return tr[rth].sum - tr[lth].sum;
    int mid = (l + r) >> 1, ans = 0;
    if(ql <= mid) ans += query(tr[lth].ls, tr[rth].ls, ql, qr, l, mid);
    if(qr > mid) ans += query(tr[lth].rs, tr[rth].rs, ql, qr, mid + 1, r);
    return ans;
}

void init(){
    for(int i = 1; i <= tot; i ++) tr[i] = {0, 0, 0};
    for(int i = 1; i <= n; i ++) T[i] = 0;
    tot = 0;
}

void solve(){
    cin >> a;
    n = a.length();

    manacher();
    init();

    ll ans = 0;
    for(int i = 1; i <= n; i ++){ // 考虑枚举回文中心
        /* 
            si = # pi 半径以内以i为中心的子串都满足条件1 考虑计算满足条件2 的个数 区间为 [l = i - p[i] + 1, r = i + p[i] - 1]
            在该区间内只要满足 j + p[j] - 1 >= i 的点j都符合要求 (i - j <= j - l)
        */
        if(s[i] == '#') { // 只有以 # 为中心的才是偶长度的回文串
            int j = (2 * i - p[i] + 1) / 2;
            ans += query(T[j - 1], T[i - 1], i, n, 1, n);
        } 
        build(T[i], T[i - 1], i + p[i] - 1, 1, n);
    }
    cout << ans << "\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int t;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

你可能感兴趣的:(杭电多校题解,算法,数据结构)