Gym - 100889I I - iChandu (回文自动机 + 马拉车)

题意: 

给你一个字符串,然后字符串中每个位置有可以换成 $. 每次只有一个位置换成 $

问当有一个位置换成 $ 的时候,不同的回文串最多有多少个,且有多少个位置换成 $ 形成的不同的回文串最多

样例:

思路:

我们可以 用 回文自动机求出来不同的回文串的个数, 

然后当我们修改一个位置成为 $ 的时候,有可能会减少回文串的个数,有可能不会. 如果这样回文串有多个,且他们不交,就不会减少.

+ 如果有多个相同的回文串,我们取左右两端的回文串,如果这两个回文串相交,那么相交的位置中放 $ ,回文串的个数就一定会减少.

     如果不相交,无论$放在什么位置,这个回文串都不会减少, 

+ 如果只有一个这样的回文串,那么这个回文串的所有位置 放 $ ,回文串的个数都会减少.

所以我们要求出来每种回文串左右两端的位置,然后看看这两个位置是不是相交,

如果不想交,那么随便加一个$ ,都不会去掉这种回文串.

如果相交的话,  差分.

处理每个不同的回文串左右两端的位置,用回文自动机就可以了. 

既然回文串会减少,那么回文串也会增加,这个就需要用 马拉车来处理增加的回文串.

#include<bits/stdc++.h>
#define rg register
#define il inline
using namespace std;
const int N = 100010;
char s[N],t[N<<1];
int n, sz, fl[N], len[N], ch[N][26], l[N], r[N], num[N], cnt[N],p[N<<1];
il int find(int x, int y) {
    return s[y] == s[y - len[x] - 1] ? x : find(fl[x], y);
}
struct node{
    int a,b;
}pos[N];
void cal() {
    for (int i = 0; i < n+5; ++i){
        fl[i] = 0; r[i] = 0;  //清零
    }
    memset(ch, 0, sizeof ch); //这个要清零
    int now = 0;
    sz = 1;
    fl[0] = 1;    fl[1] = 1;
    len[0] = 0;   len[1] = -1;
    for(rg int i = 1; i <= n; i++) {
        now = find(now, i);
        if(!ch[now][s[i] - 'a']) {
            len[++sz] = len[now] + 2;
            fl[sz] = ch[find(fl[now], i)][s[i] - 'a'];
            ch[now][s[i] - 'a'] = sz;
            num[sz] = num[fl[sz]] + 1;
        }
        now = ch[now][s[i] - 'a'];
        cnt[ now ] ++;
        pos[now].a = min(pos[now].a, i);
        pos[now].b = max(pos[now].b, i);
        l[now] = i;
    }
    for (int i = sz; i > 1; --i) {
        cnt[fl[i]] += cnt[i];
        pos[fl[i]].a = min(pos[fl[i]].a, pos[i].a);  //在 fail 指针的时候找左右两端的位置.
        pos[fl[i]].b = max(pos[fl[i]].b, pos[i].b);
        pos[i].b -= (len[i] - 1);
        if (pos[i].b <= pos[i].a){
            r[pos[i].a+1] += 1;
            r[pos[i].b] -= 1;
        }
    }
}

int init(){
    t[0] = '$'; t[1] = '#'; 
    int j = 2;
    for (int i = 1; i <= n; i++){
        t[j++] = s[i];
        t[j++] = '#';
    }
    t[j] = '%'; 
    return j;
}
void Manacher(){
    int lent = init();
    int id = 0,mx = 0;
    for (int i = 1; i < lent; i++){
        p[i] = mx > i?min(p[2*id-i],mx-i):1;
        while(t[i-p[i]] == t[i+p[i]]) p[i]++;
        if (mx < i+ p[i]){
            id = i;
            mx = i + p[i];
        }
    }
}

void solve(){
    r[1] += sz - 1;
    int ans1 = 0,ans2 = 0;
    for (int i = 1; i <= n; ++i){
        r[i] += r[i-1];
        int tmp = r[i] + p[i*2]/2;
        if (ans1 < tmp) {
            ans1 = tmp;
            ans2 = 1;
        } else if (ans1 == tmp) ans2++;
    }
    printf("%d %d\n",ans1,ans2);
}
int main() {
    int _;
    scanf("%d",&_);
    while(_--){
        s[0] = -1;
        scanf("%s", s + 1);
        n = strlen(s + 1);
        for (int i = 0; i <= n+5; ++i){
            pos[i].a = n+1; pos[i].b = 0;
        }
        cal();
        Manacher();
        solve();
    }


    return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值