zoukankan      html  css  js  c++  java
  • 马拉车--相交回文串,最长双回文串问题

    Manacher 算法流程

    1. 为了不判断奇偶,方便比较,在原字符串里添加一些特殊字符

    2. 明确几个变量,(r)代表我遍历过的位置中回文串能延伸的最远位置,(mid)(r)对应的中心位置,那么(mid)对应的左端点就是(2mid-r)(f_i)表示(i)为中心的回文半径长度

    3. 接下来就分以下情况讨论,可以自己画图理解(我并不会画图)

      1. 我当前位置(i<r),我一定能在包含他的最长回文串的对称部分找到一个在最长回文串包含范围内和他相同的回文串(因为字母本身也是一个回文串,这样我就统一说成回文串了),但是我们并不能保证在最长回文串外部他们俩还相等,所以我要在(f_{2 imes mid-i})(r-i+1)之间取较小的一个

      2. 承接我上面的(1),因为我只在最长回文串内部判断了它的回文串,回文串外部的还没有判断,所以我向两边扩展

      3. 在我判断的过程中不断更新(mid)(r)

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 5e7+10;
    char  a[maxn];
    int cnt;
    void init(){
    	char ch = getchar();cnt = 1;
        a[0]='~',a[cnt]='|';
        while(ch < 'a'||ch > 'z') ch = getchar();
        while(ch >= 'a'&&ch <= 'z') a[++cnt] = ch,a[++cnt] = '|',ch = getchar();
    }
    int f[maxn],ans;
    int main(){
    	init();
    	for(int i = 1,r = 0,mid = 0;i <= cnt;i++){
    	    if(i <= r) f[i] = min(f[(mid<<1)-i],r-i+1);
    	    while(a[i-f[i]] == a[i+f[i]]) ++f[i];
    	    if(f[i]+i > r) r = f[i]+i-1,mid = i;
    	    if(f[i] > ans) ans = f[i];
          }
          printf("%d
    ",ans-1);
    	return 0;
    }
    

    例题:

    相交的回文串对数,我们可以转化为总回文串对数-不相交的回文串对数,两个回文串不相交,那么他们中间一定有至少一个断点,枚举断点,恰好使他是被分割开的后面的回文串的开头(这样才可以保证不被重复计算),再看在他左边有多少个回文串的结尾乘法原理做就好了。

    当我们有一个半径为(r)的回文串那么我能得到的回文串个数也是(r),马拉车可以处理出以每个字符为中心的回文串半径,但是注意我们添加了字符,所以我的半径其实就是直径+1,碰巧我的半径需要向上取证,所以我的回文串总数就是(ans=sumlimits_{i}^{cnt} dfrac{f_i}{2}),我总回文串的对数就是(ans=dfrac{ans imes (ans-1)}{2})

    考虑我们如何求区间内回文串结尾的个数和一个点的回文串开头的个数,就像上面说的,当我有一个半径为(r)的回文串,其实它的左半径上的所有点都可以作为一个回文串的开头,右半径的所有点都可以作为一个回文串的结尾,其实这就是一个区间加的操作,二次差分就可以求出这个数量。

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #define int long long
    using namespace std;
    int read() {
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9') {if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9') {a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 4e6+10,mod = 51123987;
    char a[maxn];
    int cnt,n;
    void init() {
    	scanf ("%lld",&n);
    	char ch = getchar();
    	cnt = 1;
    	a[0]='~',a[cnt]='|';
    	while(ch < 'a'||ch > 'z') ch = getchar();
    	while(ch >= 'a'&&ch <= 'z') a[++cnt] = ch,a[++cnt] = '|',ch = getchar();
    }
    int f[maxn],ans;
    int l[maxn],r1[maxn];
    signed main() {
    	init();
    	for(int i = 1,r = 0,mid = 0; i <= cnt; i++) {
    		if(i <= r) f[i] = min(f[(mid<<1)-i],r-i+1);
    		while(a[i-f[i]] == a[i+f[i]]) ++f[i];
    		if(f[i]+i > r) r = f[i]+i-1,mid = i;
    	}
    	for (int i = 1;i <= cnt;i++) l[i-f[i]+1]++,l[i+1]--,r1[i+f[i]]--,r1[i]++;
    	for (int i = 1;i <= cnt;i++) (ans += f[i]/2)%=mod;
    	ans = (ans*(ans-1)/2)%mod;
    	for (int i = 1,s = 0;i <= cnt;i++){
    		l[i] += l[i-1],r1[i] += r1[i-1];
    		if (i%2 == 0) (ans -= s*l[i])%=mod,(s += r1[i])%=mod;
    	}
    	printf("%lld
    ",(ans%mod+mod)%mod);
    	return 0;
    }
    

    明确几个变量: (r_i)表示以(i)结尾的最长回文串的长度,(l_i)表示以(i)开头的最长回文串个数。

    转移方程应该还是挺好想的,当我马拉车求出所有的半径之后:(l_{i-f_i+1}=max(f_i+1,l_{i-f_i+1})),(r_{i+f_i-1}=max(f_i+1,r_{i+f_i-1}))

    但是直到上面我们只更新了两边的最长回文串长度,中间的最长回文串长度我怎么更新呢???

    枚举断点'|',因为我们在原串中间添加了字符,所以每挪动一次断点回文串长度会-2,得到转移方程:

    (r_i=max(r_i,r_{i+2}-2))(l_i=max(l_i,l_{i-2}-2))

    最长双回文串长度就等于从同一点开始和结束的两个回文串长度相加,注意我这同一点一定是特殊字符断点

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #define int long long
    using namespace std;
    int read() {
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9') {if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9') {a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 4e6+10,mod = 51123987;
    char a[maxn];
    int cnt,n;
    void init() {
    	char ch = getchar();
    	cnt = 1;
    	a[0]='~',a[cnt]='|';
    	while(ch < 'a'||ch > 'z') ch = getchar();
    	while(ch >= 'a'&&ch <= 'z') a[++cnt] = ch,a[++cnt] = '|',ch = getchar();
    }
    int f[maxn],ans;
    int l[maxn],r1[maxn];
    signed main() {
    	init();
    	for(int i = 1,r = 0,mid = 0; i <= cnt; i++) {
    		if(i <= r) f[i] = min(f[(mid<<1)-i],r-i+1);
    		while(a[i-f[i]] == a[i+f[i]]) ++f[i];
    		if(f[i]+i > r) r = f[i]+i-1,mid = i;
    	}
    	for (int i = 1;i <= cnt;i++){
    		r1[i+f[i]-1] = max(r1[i+f[i]-1],f[i]-1);
    		l[i-f[i]+1] = max(l[i-f[i]+1],f[i]-1);
    	}
    	for (int i = 1;i <= cnt;i+=2){
    		r1[i] = max(r1[i],r1[i+2]-2);
    		l[i] = max(l[i],l[i-2]-2);
    		if (r1[i]&&l[i]) ans= max(ans,l[i]+r1[i]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    ThinkPHP整合Kindeditor多图处理示例
    KindEditor用法介绍
    MySQL 1064 错误
    Nginx中虚拟主机与指定访问路径的设置方法讲解
    AJAX PHP无刷新form表单提交的简单实现(推荐)
    教PHP程序员如何找单位(全职+实习),超有用啊!
    利用正则表达式实现手机号码中间4位用星号(*)
    PHP项目做完后想上线怎么办,告诉你免费上线方法!
    备战NOIP——模板复习16
    备战NOIP——STL复习1
  • 原文地址:https://www.cnblogs.com/little-uu/p/13962035.html
Copyright © 2011-2022 走看看