目录
D. Non-Palindromic Substring :
D. Non-Palindromic Substring :
题目描述:
假设有一个字符串长度为n, 如果这个字符串没有长度为k的回文子串,那么称这个字符串为k好字符串,现在定义f(str) = k1+k2+......,如果字符串str是一个k1字符串,k2字符串......
现在给出一个长度为n的字符串和q次查询,每次查询区间 [li,ri] 问这个区间对应的字符串的f值为多少。
思路解析:
任意一个字符串一定不是1好字符串,因为每个长度为1的子串都是回文串。
假设现在字符串长度为n,k为偶数,什么情况才能满足字符串不是k好字符串 假设k==4
那么字符串有形如 a1a2a3a4a5....的结构 假设 (a1 a2 a3 a4) 和 (a2 a3 a4 a5) 是回文的,那么可以推出a1 == a2 == a3 == a4 == a5,即只有一个字符重复的字符串才可能出现 不是 k好字符串 (k为偶数) 且此时,奇数也不为好字符串。
假设现在字符串长度为n,k为奇数,什么情况才能满足字符串不是k好字符串,假设k==3
那么字符串有形如 a1a2a3a4a5.....的结构 假设(a1a2a3) 和 (a2a3a4) 回文的,那么可以推出 a1 == a3 == a5.... a2 == a4,即只要满足是两个字符循环的字符串 (如ababab...)。可以发现此时任意长度小于等于 n的奇数k, 字符串都不是k好字符串,而且单字符循环可以看作此情况的特殊情况。
那么现在整个查询情况可以分为三类:
(1) 单字符循环的字符串, 此时f值为0
(2) 双字符循环的字符串,此时f值为 2 + 4 + ..... + m (m <= n),如果n长度为奇数则不加n
(3) 啥也不是 此时f值 2 + 3 + 4 + ..... + n, 如果长度为n的字符串是回文字符串则减去n
则(2)f值可看作 2 + 4 + ... + m (m < n) (3) f值可看作 2 + 3 + ....+ (n-1) 这两种情况都去特判一下整个字符串是否为回文字符串即可。 这里判断字符串是否回文字符串有两种做法,hash或者manacher。
// 这里判断循环字符串可以利用边界判断的方法,详细思路请看代码,自己模拟即可
下面给出两种代码的代码实现:
代码一:线段树实现hash判断回文字符串
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
static int inf = (int) 2e7;
static int[] ff = new int[26];
static SegTree tree;
public static void main(String[] args) throws IOException {
pow = new long[MAXN];
pow[0] = 1;
for (int i = 1; i < MAXN; i++) {
pow[i] = pow[i-1] * base % mod;
}
Random random = new Random();
for (int i = 0; i < 26; i++) {
ff[i] = random.nextInt(235879);
}
int t = f.nextInt();
tree = new SegTree();
while (t > 0) {
solve();
t--;
}
w.flush();
w.close();
br.close();
}
public static void solve() {
n = f.nextInt();
int q = f.nextInt();
s = (" " + f.next()).toCharArray();
TreeSet<Integer> s1 = new TreeSet<>();
TreeSet<Integer> s2 = new TreeSet<>(); // 利用s1判断单字符循环, s2判断双字符循环
for (int i = 1; i < n; i++) {
if (s[i+1] != s[i]) s1.add(i);
if (i+2 <= n && s[i] != s[i+2]) s2.add(i);
}
s1.add(n+1);
s2.add(n+1);
tree.build(1, 1, n);
for (int i = 0; i < q; i++) {
int l = f.nextInt();
int r = f.nextInt();
int len = r - l + 1;
long ans = 0;
if (l == r) {w.println(0); continue;}
int a = s1.ceiling(l);
if (a >= r) {w.println(0); continue;}
else {
a = s2.ceiling(l);
if (a >= r - 1) ans = (long) ((len - 1) / 2) * ((len - 1) / 2 + 1);
else ans = (long) len * (len - 1) / 2 - 1;
}
long[] b = tree.qry(1, l, r);
if (b[0] != b[1]) ans += len;
w.println(ans);
}
}
static int base = 29;
static int mod = (int) 1e9 + 9;
static long[] pow;
static int MAXN = (int) 2e5 + 5;
static char[] s;
static int n;
public static class Node{
int l,r;
long[] sum = new long[2];
}
public static class SegTree{
Node[] t = new Node[MAXN * 4];
public SegTree(){
for (int i = 0; i < MAXN * 4; i++) {
t[i] = new Node();
}
}
public void build(int root, int l, int r){
t[root].l = l;
t[root].r=r;
if (t[root].l == t[root].r){
t[root].sum[0] = ff[s[l] - 'a'];
t[root].sum[1] = ff[s[l] - 'a'];
return;
}
int ch = root << 1;
int mid = (l + r) >> 1;
build(ch, l, mid); build(ch|1, mid+1, r);
update(root);
}
void update(int root){
int ch = root << 1;
int len1 = t[ch|1].r - t[ch|1].l + 1;
int len2 = t[ch].r - t[ch].l + 1;
t[root].sum[0] = (t[ch].sum[0] * pow[len1] % mod + t[ch|1].sum[0]) % mod;
t[root].sum[1] = (t[ch].sum[1] + t[ch|1].sum[1] * pow[len2] % mod) % mod;
}
long[] qry(int root, int l, int r){
if (t[root].l == l && t[root].r == r){
return t[root].sum;
}
int ch = root << 1; int mid = (t[root].l + t[root].r) >> 1;
if (r <= mid) return qry(ch, l, r);
else if (l > mid) return qry(ch|1, l, r);
else {
long[] a = qry(ch, l, mid);
long[] b = qry(ch|1, mid+1, r);
long[] tmp = new long[2];
int len1 = r - mid;
int len2 = mid - l + 1;
tmp[0] = (a[0] * pow[len1] % mod + b[0]) % mod;
tmp[1] = (a[1] + b[1] * pow[len2] % mod) % mod;
return tmp;
}
}
}
static PrintWriter w = new PrintWriter(new OutputStreamWriter(System.out));
static Input f = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}
代码二:manacher判断回文字符串
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
static int inf = (int) 2e7;
static int[] ff = new int[26];
public static void main(String[] args) throws IOException {
Random random = new Random();
for (int i = 0; i < 26; i++) {
ff[i] = random.nextInt(235879);
}
int t = f.nextInt();
while (t > 0) {
solve();
t--;
}
w.flush();
w.close();
br.close();
}
public static void solve() {
int n = f.nextInt();
int q = f.nextInt();
String str = f.next();
char[] s = (" " + str).toCharArray();
TreeSet<Integer> s1 = new TreeSet<>();
TreeSet<Integer> s2 = new TreeSet<>();
for (int i = 1; i < n; i++) {
if (s[i+1] != s[i]) s1.add(i);
if (i+2 <= n && s[i] != s[i+2]) s2.add(i);
}
s1.add(n+1);
s2.add(n+1);
int[] p = manacher(str.toCharArray());
for (int i = 0; i < q; i++) {
int l = f.nextInt();
int r = f.nextInt();
int len = r - l + 1;
long ans = 0;
if (l == r) {w.println(0); continue;}
int a = s1.ceiling(l);
if (a >= r) {w.println(0); continue;}
else {
a = s2.ceiling(l);
if (a >= r - 1) ans = (long) ((len - 1) / 2) * ((len - 1) / 2 + 1);
else ans = (long) len * (len - 1) / 2 - 1;
}
if (p[l + r - 1] < len) ans += len;
w.println(ans);
}
}
public static int[] manacher(char[] s){
int n = s.length;
char[] a = new char[2 * n + 1];
for (int i = 0; i < n; i++) {
a[2 * i] = '#';
a[2 * i + 1] = s[i];
}
a[2 * n] = '#';
return manacher_odd(a);
}
public static int[] manacher_odd(char[] s){
int len = 0;
int n = s.length;
int[] p = new int[n];
int r = 0;
int c = 0;
for (int i = 0; i < n; i++) {
len = r > i ? Math.min(p[2 * c - i], r - i) : 1;
while (len + i < n && i - len >= 0 && s[i + len] == s[i - len]) len++;
if (len + i > r){
c = i;
r = len + i;
}
p[i] = len;
}
for (int i = 0; i < n; i++) {
p[i]--;
}
return p;
}
static PrintWriter w = new PrintWriter(new OutputStreamWriter(System.out));
static Input f = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}