https://ac.nowcoder.com/acm/contest/11228/G
题意
你有一个长度为 n 的字符串,你可以选择删除一段后缀(可以为空),然后修改剩余串中的一个字符,使其变成回文串。
请问最后能生成多少种可能的回文串?
需要保证整个过程中的字符集仅包含小写字母。其中修改操作必须修改,但可将字符修改为原字符。
思路
修改一个元素之后,字符串的长度不变,所以对于 n 个删掉后缀后的字符串,只需要看这个字符串修改一个元素后能变成多少种,种类累加。
对于一个字符串:
- 如果已经是回文串了:如果长度为奇数,那么中间位置的元素可以随意修改,一共 26 种;如果长度为偶数,那么修改一个元素不能使其变成回文串,所以只有原串 1 种。
- 如果左右只有一个位置不匹配:不管长度为奇数还是偶数,都可以使不匹配的左边位置变成右边位置或者右边位置变成左边位置,有 2 种方案。
- 否则只修改一个元素不能使其变成回文串,有 0 种方案。
如何判断一个串是否是回文串?
将该串正着哈希一遍,然后倒过来哈希一遍,如果正反哈希值相同的话,那么这个串从前往后从后往前是相同的,便是回文的。
如何判断一个串是否是仅有一个位置不匹配的回文串?
从中间位置向外二分最大能满足的长度,找到第一个不满足的位置跳过,然后判断剩下的左右两串是否是回文的。如果是,说明只有那一个位置不满足。(或者从外向内二分最大能满足的串,找到第一个不满足的位置跳过,然后判断中间的串是否回文)
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define fi first
#define se second
#define endl '\n'
const int N = 2000010, mod = 1e9+7;
int T, n, m;
char a[N];
unsigned long long p[N], h1[N], h2[N];
int P = 131;
int queryPre(int l, int r)
{
if(l > r) return 0;
return h1[r] - h1[l-1]*p[r-l+1];
}
int queryLast(int l, int r)
{
if(l > r) return 0;
return h2[l] - h2[r+1]*p[r-l+1];
}
int midl, midr;
bool check(int len)
{
if(queryPre(midl-len+1, midl) == queryLast(midr, midr+len-1)) return 1;
return 0;
}
signed main(){
Ios;
cin >> n;
cin >> a + 1;
p[0] = 1;
for(int i=1;i<=n;i++)
{
h1[i] = h1[i-1] * P + a[i];
p[i] = p[i-1] * P;
}
for(int i=n;i>=1;i--) h2[i] = h2[i+1] * P + a[i];
int ans = 0;
for(int i=1;i<=n;i++)
{
if(queryPre(1, (i+1)/2) == queryLast(i/2+1, i))
{
if(i%2) ans += 26;
else ans ++;
}
else
{
midl = (i+1)/2, midr = i/2+1;
int l = 0, r = (i+1)/2;
while(l < r)
{
int mid = l + r + 1>>1;
if(check(mid)) l = mid;
else r = mid - 1;
}
if(queryPre(1, midl-l-1) == queryLast(midr+l+1, i)) ans += 2;
}
}
cout << ans;
return 0;
}
实现二:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define fi first
#define se second
#define endl '\n'
const int N = 2000010, mod = 1e9+7;
int T, n, m;
char a[N];
unsigned long long p[N], h1[N], h2[N];
int P = 131;
int queryPre(int l, int r)
{
if(l > r) return 0;
return h1[r] - h1[l-1]*p[r-l+1];
}
int queryLast(int l, int r)
{
if(l > r) return 0;
return h2[l] - h2[r+1]*p[r-l+1];
}
int midl, midr;
bool check1(int x, int n)
{
if(queryPre(1, x) == queryLast(n-x+1, n)) return 1;
return 0;
}
signed main(){
Ios;
cin >> n;
cin >> a + 1;
p[0] = 1;
for(int i=1;i<=n;i++)
{
h1[i] = h1[i-1] * P + a[i];
p[i] = p[i-1] * P;
}
for(int i=n;i>=1;i--) h2[i] = h2[i+1] * P + a[i];
int ans = 0;
for(int i=1;i<=n;i++)
{
if(queryPre(1, (i+1)/2) == queryLast(i/2+1, i))
{
if(i%2) ans += 26;
else ans ++;
}
else
{
int l = 0, r = (i+1)/2;
while(l < r) //找到的是满足的最右边位置
{
int mid = l + r + 1 >> 1;
if(check1(mid, i)) l = mid;
else r = mid - 1;
}
l ++; //需要把找到的位置+1才是第一个不满足的位置
if(queryPre(l+1, i-l) == queryLast(l+1, i-l)) ans += 2;
}
}
cout << ans;
return 0;
}
经验
- 不要被求方案数的题目吓倒了!
- 妙用 二分。
记一个双哈希:
typedef pair<int,int > PII;
const int mod1=201326611;
const int mod2=402653189;
const int N=2e6+10;
const int Base1=131;const int Base2 = 13331;
const int inf=0x3f3f3f;
int n,m,h,T,k,x,y,c,tot;
int pa1[N],pa2[N],pb1[N],pb2[N];
int suma1[N],suma2[N],sumb1[N],sumb2[N];
char s[N];
void Hash1(char s[],int n){
pa1[0] = pa2[0] = 1;
for(int i = 1 ; i <= n ; i ++ ){
pa1[i] = pa1[i-1] * Base1 % mod1;
suma1[i] = (suma1[i-1]*Base1 % mod1 + s[i]) % mod1;
pa2[i] = pa2[i-1] * Base2 % mod2;
suma2[i] = (suma2[i-1]*Base2 % mod2 + s[i]) % mod2;
}
for(int i = n ; i >= 1 ; i -- ){
sumb1[i] = (sumb1[i+1]*Base1 % mod1 + s[i]) % mod1;
sumb2[i] = (sumb2[i+1]*Base2 % mod2 + s[i]) % mod2;
}
}
PII query1(int l,int r){
int tot1 = (suma1[r]-suma1[l-1]*pa1[r-l+1] % mod1 + mod1 )%mod1;
int tot2 = (suma2[r]-suma2[l-1]*pa2[r-l+1] % mod2 + mod2 )%mod2;
return {tot1,tot2};
}
PII query2(int l,int r){
int tot1 = (sumb1[l]-sumb1[r+1]*pa1[r-l+1] % mod1 + mod1 )%mod1;
int tot2 = (sumb2[l]-sumb2[r+1]*pa2[r-l+1] % mod2 + mod2 )%mod2;
return {tot1,tot2};
}
双哈希板子:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
/*
双哈希:
设置两个不同的取模数,当两种取模结果相同时,两个字符串才是相同的。
相对单哈希更安全。
*/
const int N = 200010, mod = 1e9+7;
int T, n, m;
char a[N];
int h1[N], h2[N], p1[N], p2[N];
int P = 1313131;
int mod1 = 1e9 + 7, mod2 = 1e9+17;
PII query(int l, int r)
{
if(l > r) return {0, 0};
int ans1 = (h1[r] - h1[l-1] * p1[r-l+1] % mod1 + mod1) % mod1;
int ans2 = (h2[r] - h2[l-1] * p2[r-l+1] % mod2 + mod2) % mod2;
return {ans1, ans2};
}
signed main(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
p1[0] = p2[0] = 1;
for(int i=1;i<=n;i++)
{
h1[i] = (h1[i-1] * P % mod1 + a[i]) % mod1;
h2[i] = (h2[i-1] * P % mod2 + a[i]) % mod2;
p1[i] = p1[i-1] * P % mod1;
p2[i] = p2[i-1] * P % mod2;
}
if(query(1, 3) == query(4, 6)) cout << "YES\n";
else cout << "NO\n";
return 0;
}