海亮寄 7.21
前言
业精于勤荒于嬉,行成于思毁于随
正文(加餐)
字符串选讲,这是要 \(DAY^{-1}\) 的节奏
浅贴一个课件
关键词:匹配、哈希、字典树未婚前后缀、前置转化
再浅贴一下题单
T1
我听到了 Z 函数的回响
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e7+5;
string a,b;int m,n,z[N],p[N];
inline void Z(){
z[1]=n;int l=0,r=0;
for(int i=2;i<=n;i++){
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n&&b[i+z[i]]==b[z[i]+1])z[i]++;
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
}
return;
}
inline void exkmp(){
int l=0,r=0;
for(int i=1;i<=m;i++){
if(i<=r)p[i]=min(z[i-l+1],r-i+1);
while(i+p[i]<=m&&a[i+p[i]]==b[p[i]+1])p[i]++;
if(i+p[i]-1>r)l=i,r=i+p[i]-1;
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>a>>b;m=a.length(),n=b.length();a=' '+a,b=' '+b;
Z();exkmp();
int ans1=z[1]+1,ans2=p[1]+1;
for(int i=2;i<=n;i++)ans1^=(i*(z[i]+1));
for(int i=2;i<=m;i++)ans2^=(i*(p[i]+1));
cout<<ans1<<endl<<ans2<<endl;
return 0;
}
T2
热身题,循环移位(环)直接倍长(破环为链)
剩下的哈希或者 KMP 或者 EXKMP 都可以搞定!
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
string s1,s2;int m,n,z[N],p[N];
inline void Z(){
z[1]=n;int l=0,r=0;
for(int i=2;i<=n;i++){
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n&&s2[i+z[i]]==s2[z[i]+1])z[i]++;
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
}
return;
}
inline void exkmp(){
int l=0,r=0;
for(int i=1;i<=m;i++){
if(i<=r)p[i]=min(z[i-l+1],r-i+1);
while(i+p[i]<=m&&s1[i+p[i]]==s2[p[i]+1])p[i]++;
if(i+p[i]-1>r)l=i,r=i+p[i]-1;
}
return;
}
inline void solve(){
if(s1.length()<s2.length()){cout<<"no\n";return;}
s1=' '+s1+s1;s2=' '+s2;
m=s1.length()-1,n=s2.length()-1;
Z();exkmp();
// for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
// cerr<<endl;
// for(int i=1;i<=m;i++)cerr<<p[i]<<' ';
// cerr<<endl;
// cerr<<"---------------"<<endl;
for(int i=1;i<=m;i++)
if(p[i]>=n){cout<<"yes\n";return;}
cout<<"no\n";
return;
}
inline void clr(){
for(int i=1;i<=n;i++)z[i]=0;
for(int i=1;i<=m;i++)p[i]=0;
return;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
while(cin>>s1>>s2)solve(),clr();
return 0;
}
T3
(哎,不会真的有人听 CDX 讲的写字典树吧?)
注意到两个回文串拼起来还是回文串可以哈希值的形式来表示
具体来说,记回文串 \(s\) 的哈希值为 \(a\),回文串 \(t\) 的哈希值为 \(b\),有:
其中 \(B\) 表示哈希对应的 base 值
移项,观测一轮,有:
于是乎,对于每一个串,直接维护上述式子,然后丢到 map 里
答案随便维护即可
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
string s1,s2;int m,n,z[N],p[N];
inline void Z(){
z[1]=n;int l=0,r=0;
for(int i=2;i<=n;i++){
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n&&s2[i+z[i]]==s2[z[i]+1])z[i]++;
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
}
return;
}
inline void exkmp(){
int l=0,r=0;
for(int i=1;i<=m;i++){
if(i<=r)p[i]=min(z[i-l+1],r-i+1);
while(i+p[i]<=m&&s1[i+p[i]]==s2[p[i]+1])p[i]++;
if(i+p[i]-1>r)l=i,r=i+p[i]-1;
}
return;
}
inline void solve(){
if(s1.length()<s2.length()){cout<<"no\n";return;}
s1=' '+s1+s1;s2=' '+s2;
m=s1.length()-1,n=s2.length()-1;
Z();exkmp();
// for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
// cerr<<endl;
// for(int i=1;i<=m;i++)cerr<<p[i]<<' ';
// cerr<<endl;
// cerr<<"---------------"<<endl;
for(int i=1;i<=m;i++)
if(p[i]>=n){cout<<"yes\n";return;}
cout<<"no\n";
return;
}
inline void clr(){
for(int i=1;i<=n;i++)z[i]=0;
for(int i=1;i<=m;i++)p[i]=0;
return;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
while(cin>>s1>>s2)solve(),clr();
return 0;
}
T4
来自高效进阶的压迫感(据说可以点分治?)
点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1e5+10;
int n,ans;
int sum[maxn];
int tr[maxn<<5][2],cnt;
int head[maxn],tot;
struct node{
int to,nxt,val;
}edge[maxn<<1];
inline void add(int u,int v,int w){
edge[++tot].to=v;
edge[tot].val=w;
edge[tot].nxt=head[u];
head[u]=tot;
return;
}
inline void dfs(int u,int fa){
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to,w=edge[i].val;
if(v==fa){
continue;
}
sum[v]=sum[u]^w;
dfs(v,u);
}
return;
}
inline void build(int x){
int now=0;
for(int i=1<<30;i>0;i>>=1){
bool p=i&x;
if(!tr[now][p]){
tr[now][p]=++cnt;
}
now=tr[now][p];
}
return;
}
inline int query(int x){
int res=0,now=0;
for(int i=1<<30;i>0;i>>=1){
bool p=i&x;
if(tr[now][!p]){
res+=i;
now=tr[now][!p];
}else{
now=tr[now][p];
}
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
for(int i=1;i<=n;i++){
build(sum[i]);
}
for(int i=1;i<=n;i++){
ans=max(ans,query(sum[i]));
}
printf("%d\n",ans);
return 0;
}
(远古代码,写的一坨)
T5
思维降智题
字典树起手。钦定一个串是最小的,那么一定会得到若干字母的大小关系,显然可以图论建模
建模后直接转换成有向图判环问题,拓扑排序直接做做完了
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=3e4+5,M=3e5+5,S=26;
int n,nxt[M][S],ed[M],tot;string s[N];
int in[N],ans[N],tol;vi G[S];queue<int> q;
inline void ins(string s){
int len=s.length(),now=0;
for(int i=0;i<len;i++){
int x=s[i]-'a';
if(!nxt[now][x])nxt[now][x]=++tot;
now=nxt[now][x];
}
ed[now]++;
return;
}
inline bool ask(string s){
int len=s.length(),now=0;
for(int i=0;i<len;i++){
int x=s[i]-'a';
if(ed[now])return false;
for(int c=0;c<26;c++){
if(x==c||!nxt[now][c])continue;
G[x].pb(c);in[c]++;
}
now=nxt[now][x];
}
return true;
}
inline bool judge(){
while(!q.empty())q.pop();
for(int i=0;i<26;i++)
if(!in[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
for(int v:G[u]){
in[v]--;
if(!in[v])q.push(v);
}
}
for(int i=0;i<26;i++)
if(in[i])return false;
return true;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>s[i],ins(s[i]);
for(int i=1;i<=n;i++){
for(int i=0;i<26;i++)G[i].clear(),in[i]=0;
if(!ask(s[i]))continue;
if(judge())ans[++tol]=i;
}
cout<<tol<<endl;
for(int i=1;i<=tol;i++)cout<<s[ans[i]]<<endl;
return 0;
}
T6
我从未见过,如此,厚(毒)颜(瘤)无(屎)耻(山)之题
CDX:一眼类似笛卡尔树的极值分治
云落:?(又双叒叕双目失明了)
极值分治之后,考虑在通过左右半边搞出一个异或值使得其大于分治中心
嗯,很字典树
那么考虑对某一边 build 出 01 Trie,然后把另外一边放上去跑
对答案的贡献显然和二进制上的某一位的 \(0/1\) 关系有关,分讨即可(详见代码)
直接这么写,会获得 TLE 的结果
CDX:改成启发式合并就对了
云落:彳亍
然后就是一堆 shi 山的累积,一山更比一山高
(由于云落不会 \(O(n)\) 建出类似笛卡尔树的结构,所以只能手搓线段树)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+5;
int n,a[N],ls[N],rs[N];
int rt[N],cnt,nxt[N<<7][2],res[N<<7],ans;
struct Segment_tree{
struct node{int l,r,mx;}tr[N<<2];
inline int get(int x,int y){
if(x==-1)return y;
if(y==-1)return x;
int o=(a[x]>=a[y])?x:y;
return o;
}
inline void pushup(int u){
tr[u].mx=get(tr[u<<1].mx,tr[u<<1|1].mx);
return;
}
inline void build(int u,int l,int r){
// cerr<<u<<' '<<l<<' '<<r<<endl;
tr[u].l=l,tr[u].r=r;
if(l==r){tr[u].mx=l;return;}
int mid=(l+r)>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
return;
}
inline int query(int u,int ql,int qr){
int l=tr[u].l,r=tr[u].r;
if(ql<=l&&qr>=r)return tr[u].mx;
int mid=(l+r)>>1,res=-1;
if(ql<=mid)res=get(res,query(u<<1,ql,qr));
if(qr>mid)res=get(res,query(u<<1|1,ql,qr));
return res;
}
}sgt;
inline void init(int u,int l,int r){
// cerr<<u<<' '<<l<<' '<<r<<endl;
if(l==r)return;
if(l!=u)ls[u]=sgt.query(1,l,u-1);
if(u!=r)rs[u]=sgt.query(1,u+1,r);
if(ls[u])init(ls[u],l,u-1);
if(rs[u])init(rs[u],u+1,r);
return;
}
inline void ins(int rt,int num){
int now=rt;
for(int i=20;i>=0;i--){
int x=((num>>i)&1);
if(!nxt[now][x])nxt[now][x]=++cnt;
now=nxt[now][x];res[now]++;
}
return;
}
inline void ask(int rt,int num,int tar){
int now=rt;
for(int i=20;i>=0;i--){
if(!now)return;
int x=((num>>i)&1),v=((tar>>i)&1);
if(x==0&&v==0)ans+=res[nxt[now][1]],now=nxt[now][0];
else if(x==0&&v==1)now=nxt[now][1];
else if(x==1&&v==0)ans+=res[nxt[now][0]],now=nxt[now][1];
else if(x==1&&v==1)now=nxt[now][0];
}
return;
}
inline void dfs(int u,int l,int r){
if(l==r){rt[u]=++cnt;ins(rt[u],a[u]);return;}
if(ls[u])dfs(ls[u],l,u-1);
if(rs[u])dfs(rs[u],u+1,r);
if(u-l+1<=r-u+1){
rt[u]=rt[rs[u]];
ask(rt[u],a[u],a[u]);ins(rt[u],a[u]);
for(int i=l;i<=u-1;i++)ask(rt[u],a[i],a[u]);
for(int i=l;i<=u-1;i++)ins(rt[u],a[i]);
}else{
rt[u]=rt[ls[u]];
ask(rt[u],a[u],a[u]);ins(rt[u],a[u]);
for(int i=u+1;i<=r;i++)ask(rt[u],a[i],a[u]);
for(int i=u+1;i<=r;i++)ins(rt[u],a[i]);
}
return;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sgt.build(1,1,n);
// cerr<<"Zyx"<<endl;
int root=sgt.query(1,1,n);
init(root,1,n);
// cerr<<"----------------"<<endl;
// for(int i=1;i<=n;i++)cerr<<i<<' '<<ls[i]<<' '<<rs[i]<<endl;
dfs(root,1,n);
cout<<ans<<endl;
return 0;
}
/*
7
6 2 3 1 5 11 0
*/
T7
知周所众,当你看到 \(2s/500MB\) 但 \(n \le 3 \times 10^6\) 时,会出现______
没错,云落被(消音)(消音)卡常了!
但这个题算个水紫,思维链并不是很长,除了卡常一无是处的屑
字典树起手,对于每一个查询串,先在字典树里搞一个前缀
然后问题转化为在字典树的一个子树里搞事情
自然会有一些用脚代替大脑,DS 代替思考的人搞出一些神秘的三维数点……
然而根本不需要,可以考虑维护子树内部所有的链(根指向叶子)哈希
开个 unordered_map 维护,问题转化为了查询串的后缀哈希与若干哈希值的判等与计数问题
这显然是个二维数点,由于查询在修改之后,甚至可以直接把字典树摊到 dfn 上跑一个差分
其实正常来说这题就差不多了,但是还有喜闻乐见的卡常与哈希冲突环节
这部分得看造化……
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
#define pb push_back
using namespace std;
const int N=3e6+5,K=1e5+5,B=107;
const ll MOD=((1ll<<61)-1ll);
int n,Q,ans[K];string s;ll pw[N],H[N];
int nxt[N][27],tot=1,L[N],tim,R[N],rev[N];
struct node{ll v;int id,o;};
vector<ll> t[N];vector<node> vec[N];
unordered_map<ll,int> mp;
inline void init(){
pw[0]=1;
for(int i=1;i<N;i++)pw[i]=((LL)pw[i-1]*B)%MOD;
return;
}
inline ll geth(int l,int r){
return (H[r]-(LL)H[l-1]*pw[r-l+1]%MOD+MOD)%MOD;
}
inline void ins(int len){
int now=1;t[now].pb(geth(1,len));
for(int i=1;i<=len;i++){
int c=s[i]-'a'+1;
if(!nxt[now][c])nxt[now][c]=++tot;
now=nxt[now][c];
t[now].pb((i==len)?0:geth(i+1,len));
}
return;
}
inline void dfs(int u){
L[u]=++tim;rev[tim]=u;
for(int i=1;i<=26;i++)
if(nxt[u][i])dfs(nxt[u][i]);
R[u]=tim;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
init();
cin>>n>>Q;
for(int i=1;i<=n;i++){
cin>>s;int len=s.length();s=' '+s;
for(int j=1;j<=len;j++)H[j]=((LL)H[j-1]*B+s[j])%MOD;
ins(len);
}
dfs(1);
for(int i=1;i<=Q;i++){
cin>>s;int len=s.length();s=' '+s;
int now=1,idx=0;
for(int j=1;j<=len&&s[j]!='*';j++){
int c=s[j]-'a'+1;
if(!nxt[now][c]){ans[i]=-1;break;}
idx=j;now=nxt[now][c];
}
if(ans[i]==-1)continue;
ll sum=0;
for(int j=idx+2;j<=len;j++)sum=((LL)sum*B+s[j])%MOD;
vec[L[now]-1].pb({sum,i,-1});vec[R[now]].pb({sum,i,1});
}
for(int i=1;i<=tim;i++){
for(ll x:t[rev[i]])mp[x]++;
for(auto x:vec[i])ans[x.id]+=(mp[x.v]*x.o);
}
for(int i=1;i<=Q;i++)cout<<max(0,ans[i])<<'\n';
return 0;
}
T8
(高效进阶二连击破)
感觉出题人是九漏鱼,语文是 oier 教的
KMP 的基础上重定义 == 的条件,树状数组维护排名
不会有人回去写主席树吧
点击查看代码
#include<bits/stdc++.h>
#define lwbd lower_bound
using namespace std;
const int N=1e6+5;
int n,m,p[N],a[N],b[N],v[N],nxt[N],tol,ans[N];
struct BIT{
int c[N];
inline int lb(int x){return x&(-x);}
inline void clr(){
for(int i=1;i<N;i++)c[i]=0;
return;
}
inline void add(int x,int v){
for(int i=x;i<N;i+=lb(i))c[i]+=v;
return;
}
inline int ask(int x){
int res=0;
for(int i=x;i;i-=lb(i))res+=c[i];
return res;
}
}bit;
inline void getnxt(){
bit.clr();int j=0;
for(int i=2;i<=n;i++){
while(j>=1&&bit.ask(p[i])!=v[j+1]){
for(int x=i-j;x<i-nxt[j];x++)bit.add(p[x],-1);
j=nxt[j];
}
if(bit.ask(p[i])==v[j+1])bit.add(p[i],1),j++;
nxt[i]=j;
}
return;
}
inline void kmp(){
bit.clr();int j=0;
for(int i=1;i<=m;i++){
while(j>=1&&bit.ask(a[i])!=v[j+1]){
for(int x=i-j;x<i-nxt[j];x++)bit.add(a[x],-1);
j=nxt[j];
}
if(bit.ask(a[i])==v[j+1])bit.add(a[i],1),j++;
if(j==n)ans[++tol]=i-n+1;
}
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;v[n+1]=-1;
for(int i=1,x;i<=n;i++)cin>>x,p[x]=i;
for(int i=1;i<=m;i++)cin>>a[i],b[i]=a[i];
sort(b+1,b+m+1);int T=unique(b+1,b+m+1)-b-1;
for(int i=1;i<=m;i++)a[i]=lwbd(b+1,b+T+1,a[i])-b;
for(int i=1;i<=n;i++)v[i]=bit.ask(p[i]),bit.add(p[i],1);
getnxt();kmp();
cout<<tol<<'\n';
for(int i=1;i<=tol;i++)cout<<ans[i]<<' ';
cout<<'\n';
return 0;
}
T9
完全一样的题面,把 KMP 修改成 EXKMP 就对了,吗?
死因:字符串拼接之后没有判断 LCP 越界的情况
墨鱼死因:没有判断z[i]顶到边界的情况
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,m,a[N],v1[N],v2[N],z[N];
struct BIT{
int c[N];
inline int lb(int x){return x&(-x);}
inline void clr(){memset(c,0,sizeof(c));return;}
inline void add(int x,int v){
for(int i=x;i<N;i+=lb(i))c[i]+=v;
return;
}
inline int ask(int x){
int res=0;
for(int i=x;i;i-=lb(i))res+=c[i];
return res;
}
}bit;
inline bool chk(int p1,int p2){
return v1[p1]==bit.ask(a[p2]-1)&&v2[p1]==bit.ask(a[p2]);
}
inline void getz(){
z[1]=n;int l=1,r=1;
// for(int i=1;i<=n;i++)cerr<<a[i]<<' ';
// cerr<<endl;
for(int i=2;i<=n;i++){
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
if(z[i]==r-i+1){
while(i+z[i]<=n&&z[i]+1<=n-m&&chk(z[i]+1,i+z[i])){
bit.add(a[i+z[i]],1);
z[i]++;
}
}
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
bit.add(a[i],-1);
}
for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
cerr<<endl;
return;
}
signed main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=n+1;i<=n+m;i++)cin>>a[i];
n+=m;
for(int i=1;i<=n;i++){
v1[i]=bit.ask(a[i]-1);v2[i]=bit.ask(a[i]);
bit.add(a[i],1);
}
bit.clr();getz();
for(int i=n-m+1;i<=n;i++)cout<<z[i]<<' ';
cout<<'\n';
return 0;
}
/*
5 15
1 5 5 2 5
2 13 10 4 11 5 2 6 11 4 9 6 11 6 1
*/
T10
一句话题意:求最短 border
直接先求出 nxt 数组,然后递归找最短 border
但是递归计算所有的最短 border 会超时,考虑浅浅地记忆化一手
就过了……
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,nxt[N],ans;string s;
inline void getnxt(){
int j=0;
for(int i=2;i<=n;i++){
while(s[i]!=s[j+1]&&j>=1)j=nxt[j];
if(s[i]==s[j+1])j++;
nxt[i]=j;
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>s;s=' '+s;
getnxt();
for(int i=2,j=2;i<=n;i++,j=i){
while(nxt[j])j=nxt[j];
if(nxt[i])nxt[i]=j;
ans+=i-j;
}
cout<<ans<<endl;
return 0;
}
T11
谁懂啊被 map 和模数类型为 long long 背刺的破碎感
好不容易卡常之后,给我直接来一手
Wrong Answer on #61
(消音)哈希冲突,我(消音)(消音)(消音)
吐槽时间结束,进入正题
枚举 \(l,r\) 然后直接用状压异或的方式判断是否行上有回文
如果有,那么直接搞一个集合(可重复)哈希
因为列回文等价于相对应的行可重集合相等
所以集合哈希搞出来,在列上跑 Manacher 即可
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=255,MOD=1e8+7,OwO=10086;
int n,m,rnd[30];string str[N];
bool vis[N][N][N];int H[N][N][N];
unordered_map<int,int> mp;int occ=1022,tol;
int s[N<<1],S[N],p[N<<1],ans;
mt19937 RD(time(NULL));
inline int ppc(int x){return __builtin_popcount(x);}
inline int Manacher(int n){
n=n*2+1;
for(int i=1;i<=n;i++)s[i]=((i&1)?OwO:S[i>>1]);
int c=0,r=0,len=0;
for(int i=1;i<=n;i++){
len=(r>i?min(p[c*2-i],r-i):1);
while(i+len<=n&&i-len>=1&&s[i+len]==s[i-len])len++;
if(i+len>r)r=i+len,c=i;
p[i]=len;
}
int res=0;
for(int i=1;i<=n;i++){
res+=p[i];res-=(p[i]+1)/2;
}
return res;
}
inline void work(int l,int r){
int len=0;
for(int i=1;i<=n;i++){
if(!mp[H[i][l][r]]){
ans+=Manacher(len);len=0;
continue;
}
S[++len]=mp[H[i][l][r]];
}
ans+=Manacher(len);len=0;
return;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
for(int i=1;i<=26;i++){
uniform_int_distribution<int> V(19800107,20091023);
rnd[i]=V(RD);
}
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>str[i],str[i]=' '+str[i];
for(int i=1;i<=n;i++){
for(int l=1;l<=m;l++){
int st=(1<<(str[i][l]-'a'+1));
int ad=rnd[(str[i][l]-'a'+1)];ll ml=rnd[(str[i][l]-'a'+1)];
vis[i][l][l]=true;H[i][l][l]=(0ll+ml+ad)%MOD;
if(!mp[H[i][l][l]])mp[H[i][l][l]]=++tol;
for(int r=l+1;r<=m;r++){
int c=str[i][r]-'a'+1;
st^=(1<<c);ad=(ad+rnd[c])%MOD;ml=(1ll*ml*rnd[c])%MOD;
if(st==0||ppc(st)==1){
vis[i][l][r]=true;H[i][l][r]=(0ll+ml+ad)%MOD;
if(!mp[H[i][l][r]])mp[H[i][l][r]]=++tol;
}
}
}
}
for(int l=1;l<=m;l++)
for(int r=l;r<=m;r++)
work(l,r);
cout<<ans<<'\n';
return 0;
}
T12
不会,挖坑,待填
Upd. 突然就会了
不需要搞一些奇怪的回文判定,故抛弃 PAM,拥抱 Manacher
起手 Manacher,接下来所有的字符串均指扩展串。先刨除单个分隔符作为回文串出现的贡献(扩展串)
考虑单次查询 \([l,r]\) 怎么做
对于 \(\forall i \in [l,r]\),其对答案的贡献为 \(\min (p_i ,\ i-l ,\ r-i)\)
三个值不好做,直接拆分成 \([l,mid]\) 和 \([mid+1,r]\) 两部分,去掉一维限制去做
\([l,mid]\) 和 \([mid+1,r]\) 本质上是对称的,只做一边,另一边照搬就行
现在问题转化为,对于 \(\forall i \in [l,mid]\),对答案的贡献为 \(\min (p_i ,\ i-l)\)
经典线段树维护回文子串,一个 \(p_i\) 相当于在 \([i-p_i+1,i]\) 做区间加 \(1\)
多次查询咋办?离线扫描线直接做做完了
感觉没有 CDX 说的那么难写是怎么肥四捏?
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+5;
int n,Q,p[N],ans[N];char str[N],s[N];
struct que{int l,r,id;}q1[N],q2[N];
struct Segment_tree{
struct node{int l,r,sum,tag;}tr[N<<2];
inline void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
return;
}
inline void pushdown(int u){
if(!tr[u].tag)return;
int v=tr[u].tag;
tr[u<<1].sum+=((tr[u<<1].r-tr[u<<1].l+1)*v);
tr[u<<1|1].sum+=((tr[u<<1|1].r-tr[u<<1|1].l+1)*v);
tr[u<<1].tag+=v;tr[u<<1|1].tag+=v;
tr[u].tag=0;
return;
}
inline void build(int u,int l,int r){
tr[u].l=l;tr[u].r=r;tr[u].tag=0;
if(l==r){tr[u].sum=0;return;}
int mid=(l+r)>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
return;
}
inline void modify(int u,int ql,int qr,int k){
int l=tr[u].l,r=tr[u].r;
if(ql<=l&&qr>=r){
tr[u].sum+=((r-l+1)*k);
tr[u].tag+=k;
return;
}
pushdown(u);
int mid=(l+r)>>1;
if(ql<=mid)modify(u<<1,ql,qr,k);
if(qr>mid)modify(u<<1|1,ql,qr,k);
pushup(u);
return;
}
inline int query(int u,int ql,int qr){
int l=tr[u].l,r=tr[u].r;
if(ql<=l&&qr>=r)return tr[u].sum;
pushdown(u);
int mid=(l+r)>>1,res=0;
if(ql<=mid)res+=query(u<<1,ql,qr);
if(qr>mid)res+=query(u<<1|1,ql,qr);
return res;
}
}sgt;
inline bool cmp1(que x,que y){return x.r<y.r;}
inline bool cmp2(que x,que y){return x.l>y.l;}
inline void Manacher(){
n=n*2+1;
for(int i=1;i<=n;i++)s[i]=((i&1)?'#':str[i>>1]);
int c=0,r=0,len=0;
for(int i=1;i<=n;i++){
len=(r>i?min(p[c*2-i],r-i):1);
while(i+len<=n&&i-len>=1&&s[i+len]==s[i-len])len++;
if(i+len>r)r=i+len,c=i;
p[i]=len;
}
return;
}
inline void work_l(){
sort(q1+1,q1+Q+1,cmp1);
sgt.build(1,1,n);int j=1;
for(int i=1;i<=n;i++){
sgt.modify(1,i-p[i]+1,i,1);
while(j<=Q&&q1[j].r<=i){
ans[q1[j].id]+=sgt.query(1,q1[j].l,q1[j].r);
j++;
}
}
return;
}
inline void work_r(){
sort(q2+1,q2+Q+1,cmp2);
sgt.build(1,1,n);int j=1;
for(int i=n;i>=1;i--){
sgt.modify(1,i,i+p[i]-1,1);
while(j<=Q&&q2[j].l>=i){
ans[q2[j].id]+=sgt.query(1,q2[j].l,q2[j].r);
j++;
}
}
return;
}
signed main(){
// freopen("qwq.in","r",stdin);
// freopen("qwq.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>Q>>(str+1);
Manacher();
for(int i=1;i<=n;i++)cerr<<p[i]<<' ';
for(int i=1;i<=Q;i++){
int l,r;cin>>l>>r;ans[i]-=(r-l+2);
l<<=1;l--;r<<=1;r++;
int mid=(l+r)>>1;
q1[i]={l,mid,i},q2[i]={mid+1,r,i};
}
work_l(),work_r();
for(int i=1;i<=Q;i++)cout<<ans[i]/2<<'\n';
return 0;
}
后记
世界孤立我任它奚落
完结撒花!

浙公网安备 33010602011771号