Manacher算法、扩展Kmp

求一段字符串中的最长回文字符串

暴力方法:枚举每个点不停的向左右两边扩展,但这样偶数的回文串不能求出来

解决:在每两个字符之间及字符串的左右两头都插上任意字符(扩展串),然后枚举每个点向左右两边扩展,得到的扩展半径-1就是回文串长度

   P3805 【模板】manacher - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {

	public static int MAXN = 11000001;

	public static char[] ss = new char[MAXN << 1];

	public static int[] p = new int[MAXN << 1];

	public static int n;

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		out.println(manacher(in.readLine()));
		out.flush();
		out.close();
		in.close();
	}

	public static int manacher(String str) {
		manacherss(str.toCharArray());
		int max = 0;
		for (int i = 0, c = 0, r = 0, len; i < n; i++) {
			len = r > i ? Math.min(p[2 * c - i], r - i) : 1;
			while (i + len < n && i - len >= 0 && ss[i + len] == ss[i - len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			max = Math.max(max, len);
			p[i] = len;
		}
		return max - 1;
	}

	public static void manacherss(char[] a) {
		n = a.length * 2 + 1;
		for (int i = 0, j = 0; i < n; i++) {
			ss[i] = (i & 1) == 0 ? '#' : a[j++];
		}
	}

}

扩展Kmp

 z数组:一个字符串从每个位置开始的字串和原字符串开头匹配上的最长公共前缀长度

 e数组:字符串a的每一个位置和字符串b的开头匹配上的最长公共前缀长度 

P5410 【模板】扩展 KMP/exKMP(Z 函数) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {

	public static int MAXN = 20000001;

	public static int[] z = new int[MAXN];

	public static int[] e = new int[MAXN];

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		char[] a = in.readLine().toCharArray();
		char[] b = in.readLine().toCharArray();
		zArray(b, b.length);
		eArray(a, b, a.length, b.length);
		out.println(eor(z, b.length));
		out.println(eor(e, a.length));
		out.flush();
		out.close();
		in.close();
	}

	// 非常像Manacher算法
	public static void zArray(char[] s, int n) {
		z[0] = n;
		for (int i = 1, c = 1, r = 1, len; i < n; i++) {
			len = r > i ? Math.min(r - i, z[i - c]) : 0;
			while (i + len < n && s[i + len] == s[len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			z[i] = len;
		}
	}

	// 非常像Manacher算法
	public static void eArray(char[] a, char[] b, int n, int m) {
		for (int i = 0, c = 0, r = 0, len; i < n; i++) {
			len = r > i ? Math.min(r - i, z[i - c]) : 0;
			while (i + len < n && len < m && a[i + len] == b[len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			e[i] = len;
		}
	}

	public static long eor(int[] arr, int n) {
		long ans = 0;
		for (int i = 0; i < n; i++) {
			ans ^= (long) (i + 1) * (arr[i] + 1);
		}
		return ans;
	}

}

   

 3031. 将单词恢复初始状态所需的最短时间 II - 力扣(LeetCode)

从第一个字符开始枚举间隔k长度的字符是否和原字符串的前缀完全相同:如果相同,那么跳过几个间隔就花费几秒;如果到字符串最后还没匹配上,那么字符串的总长度除以向上取整就是花费的时间。所以用z数组就可以解决

public class Solution {

	public static int minimumTimeToInitialState(String word, int k) {
		char[] s = word.toCharArray();
		int n = s.length;
		zArray(s, n);
		for (int i = k; i < n; i += k) {
			if (z[i] == n - i) {
				return i / k;
			}
		}
		return (n + k - 1) / k;
	}

	// leetcode增加了数据量
	// 所以把这个值改成10^6规模
	public static int MAXN = 1000001;

	public static int[] z = new int[MAXN];

	public static void zArray(char[] s, int n) {
		z[0] = n;
		for (int i = 1, c = 1, r = 1, len; i < n; i++) {
			len = r > i ? Math.min(r - i, z[i - c]) : 0;
			while (i + len < n && s[i + len] == s[len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			z[i] = len;
		}
	}

}

 

 5. 最长回文子串 - 力扣(LeetCode)

class Solution {
public:
    static const int maxn=1001;
    int p[maxn<<1];
    int end=0,maxnum=0;
    void get_manacher(string s){
        for(int i=0,r=0,c=0,len;i<s.size();i++){
            len=r>i?min(p[2*c-i],r-i):1;
            while(i+len<s.size()&&i-len>=0&&s[len+i]==s[i-len])
            len++;
            if(i+len>r){
                r=i+len;
                c=i;
            }
            if(len>maxnum){
                maxnum=len;
                end=(i+len-1);
            }
            p[i]=len;
        }
    }
    string longestPalindrome(string s) {
         string cur="#";
         for(int i=0;i<s.size();i++){
            cur+=s[i];
            cur+='#';
         }      
         get_manacher(cur);
         int epos=end/2;
         int bpos=epos-(maxnum-1);
         string ans;
         for(int i=bpos;i<epos;i++)
         ans+=s[i];
         return ans;

    }
};

manacher的过程中记录最长的回文半径的结束位置,然后除2是原始字符串的终止位置,往前推原始字符串的长度截取字串即可

647. 回文子串 - 力扣(LeetCode)

class Solution {
public:
    static const int maxn=1001;
    int p[maxn<<1];
    void get_mancher(string s){
        for(int i=0,r=0,c=0,len;i<s.size();i++){
            len=r>i?min(p[2*c-i],r-i):1;
            while(i+len<s.size()&&i-len>=0&&s[i+len]==s[i-len])
            len++;
            if(i+len>r){
                r=i+len;
                c=i;
            }
            p[i]=len;
        }
    }
    int countSubstrings(string s) {
        string cur="#";
        for(int i=0;i<s.size();i++){
            cur+=s[i];
            cur+="#";
        }
        get_mancher(cur);
        int ans=0;
        for(int i=0;i<cur.size();i++)
        ans+=p[i]/2;
        return ans;

    }
};

回文半径长度/2就是原始字符串中一个回文串中含有的回文串个数

  2472. 不重叠回文子字符串的最大数目 - 力扣(LeetCode)

class Solution {
public:
   static const int maxn=2001;
   int p[maxn<<1];
   string cur;
   int manacher(int l,int k){
      for(int i=l,r=l,c=l,len;i<cur.size();i++){
         len=r>i?min(p[2*c-i],r-i):1;
         while(i+len<cur.size()&&i-len>=l&&cur[i-len]==cur[i+len]){
            if(++len>k)
            return i+k+(cur[i+k]=='#'?0:1);
         }
         if(i+len>r){
            r=len+i;
            c=i;
         }
         p[i]=len;
      }
      return -1;
   }
   int f(int k){
      int ans=0;
      int nex=0;
      while((nex=manacher(nex,k))!=-1)
      ans++;
      return ans;
     
   }
    int maxPalindromes(string s, int k) {
         cur="#";
        for(int i=0;i<s.size();i++){
            cur+=s[i];
            cur+="#";
        }
        return f(k);
    }
};

为了更多的切出大于等于k长度的回文串,只要遇到长度满足的回文串,就加1,且返回距离结束位置最近的‘#’字符的位置,从此位置开始重新进行mancher算法,最终就会得到最多的回文串

 P1659 [国家集训队] 拉拉队排练 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

// 长度前k名的奇数长度回文子串长度乘积
// 给定一个字符串s和数值k,只关心所有奇数长度的回文子串
// 返回其中长度前k名的回文子串的长度乘积是多少
// 如果奇数长度的回文子串个数不够k个,返回-1
// 测试链接 : https://www.luogu.com.cn/problem/P1659

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {

	public static int MOD = 19930726;

	public static int MAXN = 10000001;

	public static String[] mk;

	public static int m, n;

	public static long k;

	public static char[] ss = new char[MAXN << 1];

	public static int[] p = new int[MAXN << 1];

	public static int[] cnt = new int[MAXN];

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		mk = in.readLine().split(" ");
		m = Integer.valueOf(mk[0]);
		k = Long.valueOf(mk[1]);
		out.println(compute(in.readLine()));
		out.flush();
		out.close();
		in.close();
	}

	public static int compute(String s) {
		manacher(s);
		for (int i = 1; i < n; i += 2) {
			cnt[p[i] - 1]++;
		}
		long ans = 1;
		long sum = 0;
		for (int len = (m & 1) == 1 ? m : (m - 1); len >= 0 && k >= 0; len -= 2) {
			sum += cnt[len];
			ans = (ans * power(len, Math.min(k, sum))) % MOD;
			k -= sum;
		}
		return k < 0 ? (int) ans : -1;
	}

	public static void manacher(String str) {
		manacherss(str.toCharArray());
		for (int i = 0, c = 0, r = 0, len; i < n; i++) {
			len = r > i ? Math.min(p[2 * c - i], r - i) : 1;
			while (i + len < n && i - len >= 0 && ss[i + len] == ss[i - len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			p[i] = len;
		}
	}

	public static void manacherss(char[] a) {
		n = a.length * 2 + 1;
		for (int i = 0, j = 0; i < n; i++) {
			ss[i] = (i & 1) == 0 ? '#' : a[j++];
		}
	}

	public static long power(long x, long n) {
		long ans = 1;
		while (n > 0) {
			if ((n & 1) == 1) {
				ans = (ans * x) % MOD;
			}
			x = (x * x) % MOD;
			n >>= 1;
		}
		return ans;
	}

}

manacher算出每个中心的最长回文串,统计以‘#’字符为中心的各回文长度的词频(因为不以‘#’为中心的回文串一定不是奇数串),奇数串最大长度不超过原子符串长度,从最大的奇数串开始向下依次累乘(乘法快速幂)。注意:长度更大的回文串中一定包含长度较小的各奇数回文串,所以乘的时候词频要向下传递

P4555 [国家集训队] 最长双回文串 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

// 最长双回文串长度
// 输入字符串s,求s的最长双回文子串t的长度
// 双回文子串就是可以分成两个回文串的字符串
// 比如"aabb",可以分成"aa"、"bb"

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {

	public static int MAXN = 100002;

	public static char[] ss = new char[MAXN << 1];

	public static int[] p = new int[MAXN << 1];

	public static int[] left = new int[MAXN << 1];

	public static int[] right = new int[MAXN << 1];

	public static int n;

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		out.println(compute(in.readLine()));
		out.flush();
		out.close();
		in.close();
	}

	public static int compute(String s) {
		manacher(s);
		for (int i = 0, j = 0; i < n; i++) {
			while (i + p[i] > j) {//最先影响到的一定是最长的,就统计
				left[j] = j - i;
				j += 2;
			}
		}
		for (int i = n - 1, j = n - 1; i >= 0; i--) {
			while (i - p[i] < j) {
				right[j] = i - j;
				j -= 2;
			}
		}
		int ans = 0;
		for (int i = 2; i <= n - 3; i += 2) {
			ans = Math.max(ans, left[i] + right[i]);
		}
		return ans;
	}

	public static void manacher(String str) {
		manacherss(str.toCharArray());
		for (int i = 0, c = 0, r = 0, len; i < n; i++) {
			len = r > i ? Math.min(p[2 * c - i], r - i) : 1;
			while (i + len < n && i - len >= 0 && ss[i + len] == ss[i - len]) {
				len++;
			}
			if (i + len > r) {
				r = i + len;
				c = i;
			}
			p[i] = len;
		}
	}

	public static void manacherss(char[] a) {
		n = a.length * 2 + 1;
		for (int i = 0, j = 0; i < n; i++) {
			ss[i] = (i & 1) == 0 ? '#' : a[j++];
		}
	}

}

预处理得到left数组记录每个‘#’字符左边的最大紧贴回文串长度和right数组记录紧贴‘#’字符右边的最大回文串长度,最后统计最大值

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值