【后缀数组/SAM+边分树合并】LGP5115 Check,Check,Check one two!

news/2024/11/29 14:53:06/

【题目】
原题地址
给定一个字符串 S S S,求 ∑ 1 ≤ i &lt; j ≤ n l c p ( i , j ) l c s ( i , j ) [ l c p ( i , j ) ≤ k 1 ] [ l c s ( i , j ) ≤ k 2 ] \sum_{1\leq i&lt;j\leq n}lcp(i,j)lcs(i,j)[lcp(i,j)\leq k_1][lcs(i,j)\leq k_2] 1i<jnlcp(i,j)lcs(i,j)[lcp(i,j)k1][lcs(i,j)k2]
其中 l c p ( i , j ) lcp(i,j) lcp(i,j)表示第 i i i个后缀和第 j j j个后缀的 l c p lcp lcp l c s ( i , j ) lcs(i,j) lcs(i,j)表示第 i i i个前缀和第 j j j个前缀的 l c s lcs lcs
∣ S ∣ ≤ 1 0 5 |S|\leq 10^5 S105

【解题思路】
解法一:后缀数组
由于题目中定义的 l c p lcp lcp l c s lcs lcs是以一个点为中心往两边计算的,我们不妨将它们拼在一起。具体来说,假设我们知道 l c p ( i , j ) lcp(i,j) lcp(i,j) l c s ( i , j ) lcs(i,j) lcs(i,j),那么我们不难得到: S [ i − l c p ( i , j ) + 1 … i + l c s ( i , j ) − 1 ] = S [ j − l c p ( i , j ) + 1 … j + l c s ( i , j ) − 1 ] S[i-lcp(i,j)+1\dots i+lcs(i,j)-1]=S[j-lcp(i,j)+1\dots j+lcs(i,j)-1] S[ilcp(i,j)+1i+lcs(i,j)1]=S[jlcp(i,j)+1j+lcs(i,j)1],且 S [ i − l c p ( i , j ) ] ≠ S [ j − l c p ( i , j ) ] , S [ i + l c s ( i , j ) ] ≠ S [ j + l c s ( i , j ) ] S[i-lcp(i,j)]\neq S[j-lcp(i,j)],S[i+lcs(i,j)]\neq S[j+lcs(i,j)] S[ilcp(i,j)]̸=S[jlcp(i,j)],S[i+lcs(i,j)]̸=S[j+lcs(i,j)]
于是我们对 S S S建出后缀数组,那么我们要考虑的就是每两个后缀之间的贡献。显然两个后缀 i , j i,j i,j有贡献当且仅当它们的 l c p lcp lcp不为 0 0 0 S [ i − 1 ] ≠ S [ j − 1 ] S[i-1]\neq S[j-1] S[i1]̸=S[j1]
这样做法就比较显然了:每个后缀 i i i要记录 S [ i − 1 ] S[i-1] S[i1]是什么字符,按 height \text{height} height从大到小拆隔板进行启发式合并,考虑有多少对贡献的时候直接容斥一下,然后乘上长度为当前 height \text{height} height的贡献即可。

现在我们要求长度 l e n len len的串贡献是多少。
实际上它的贡献就是 ∑ i = 1 k 1 i ( l e n − i + 1 ) − ∑ i = 1 l e n − k 2 ( l e n − i + 1 ) \sum_{i=1}^{k_1}i(len-i+1)-\sum_{i=1}^{len-k_2}(len-i+1) i=1k1i(leni+1)i=1lenk2(leni+1)
(可能会有一些偏差以及边界问题,可以看代码)
这个东西可以预处理出来,这样我们就可以解决这道题了。

解法二: SAM + \text{SAM}+ SAM+边分树合并
我们对这个串的正序和逆序分别建出 SAM \text{SAM} SAM,考虑答案的计算。
根据 SAM \text{SAM} SAM的性质,两个前缀节点的 l c s lcs lcs就是这两个节点 l c a lca lca m x mx mx,后缀同理,那么如果把第一棵树大于 k 1 k_1 k1的节点置零,第二棵同理,问题就转化为了给定两棵树,询问所有点对 ( u , v ) (u,v) (u,v)在两棵树上 l c a lca lca m x mx mx的乘积之和。

考虑在第一棵树上枚举 l c a lca lca进行贡献,那么可以一个个合并 l c a lca lca的每个子树,考虑跨越两个连通块的点对在第二棵树上的 l c a lca lca m x mx mx值。

这个东西用边分树可以解决,具体点来讲我们将深度较低的一个联通块看成是右儿子而深度较大的一个联通块看成左儿子,然后每一个边分树节点维护一下左儿子中的节点个数和右儿子联通块中可能成为lca节点的点权和,然后合并的时候相乘一下就可以统计贡献了。

但这个东西我并不会写,照着别人的代码写,但发现重构树的时候我用自己的写法在合并答案的时候好像出现了一些小差错(WA了一个点),我并不知道为什么。希望有好心人来解释一下qwq。

【参考代码1】(SA)

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define se second
using namespace std;typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10;
int n,k1,k2;
int a[N],lp[N],rp[N],siz[N],bl[N],cnt[N][28];
char s[N];
pii h[N];
ull ans,f[N];namespace SA
{int rk[N],hi[N];int wa[N],wb[N],wx[N],wy[N],sa[N];bool cmp(int *r,int a,int b,int l){return r[a]==r[b] && r[a+l]==r[b+l];}void getsa(int *r,int n,int m){int *x=wa,*y=wb,*t,i,j,p;for(i=0;i<m;++i) wx[i]=0;for(i=0;i<n;++i) wx[x[i]=r[i]]++;for(i=1;i<m;++i) wx[i]+=wx[i-1];for(i=n-1;~i;--i) sa[--wx[x[i]]]=i;for(j=1,p=0;p<n;j<<=1,m=p){for(p=0,i=n-j;i<n;++i) y[p++]=i;for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;for(i=0;i<m;++i) wx[i]=0;for(i=0;i<n;++i) wx[wy[i]=x[y[i]]]++;for(i=1;i<m;++i) wx[i]+=wx[i-1];for(i=n-1;~i;--i) sa[--wx[wy[i]]]=y[i];for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}}void getheight(int *r){int i,j,k=0;for(i=1;i<=n;++i) rk[sa[i]]=i;for(i=0;i<n;hi[rk[i++]]=k)for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];++k);}void adjust(){for(int i=1;i<=n;++i) sa[i]++;for(int i=n;i;--i) rk[i]=rk[i-1];sa[0]=rk[0]=0;}void output(){for(int i=0;i<=n;++i) printf("%d ",sa[i]); puts("");for(int i=0;i<=n;++i) printf("%d ",rk[i]); puts("");for(int i=0;i<=n;++i) printf("%d ",hi[i]); puts("");}
}
using namespace SA;ull sq(int x){return (ull)(x+1)*x/2;}
ull sq2(int x){return (ull)x*(2*x+1)*(x+1)/6;}
ull getf(int x)
{if(x>=k1+k2)return 0;ull s1=(ull)sq(min(x,k1))*(x+1)-sq2(min(x,k1));ull s2=(ull)sq(max(0,x-k2))*(x+1)-sq2(max(0,x-k2));//printf("%d %d %d %llu %llu\n",x,k1,k2,s1,s2);return (ull)s1-s2;
}void init()
{scanf("%s%d%d",s,&k1,&k2);n=strlen(s);k1=min(k1,n);k2=min(k2,n);//printf("%llu %llu %llu\n",n,k1,k2);for(int i=1;i<=n;++i) f[i]=getf(i);//for(int i=1;i<=n;++i) printf("%llu\n",f[i]);for(int i=0;i<n;++i) a[i]=s[i]-'a'+1;getsa(a,n+1,30);getheight(a);adjust();//output();for(int i=n;i;--i) a[i]=a[i-1]; a[0]=0;//for(int i=1;i<=n;++i) printf("%d",a[i]);puts("");for(int i=1;i<=n;++i) lp[i]=rp[i]=bl[i]=i,siz[i]=1,cnt[i][a[sa[i]-1]]++;//for(int i=1;i<=n;++i) printf("%d %d\n",sa[i],a[sa[i]-1]);for(int i=2;i<=n;++i) h[i-1]=mkp(-hi[i],i);sort(h+1,h+n);
}ull calc(int x,int y)
{ull res=(ull)siz[x]*siz[y];for(int i=1;i<=26;++i) res-=(ull)cnt[x][i]*cnt[y][i];//printf("val:%d %d %llu\n",x,y,res);return res; 
}void merge(int x,int y)
{for(int i=1;i<=26;++i) cnt[y][i]+=cnt[x][i];for(int i=lp[x];i<=rp[x];++i) bl[i]=y;lp[y]=min(lp[y],lp[x]);rp[y]=max(rp[y],rp[x]);siz[y]+=siz[x];
}void solve()
{for(int i=1;i<n;++i) {int len=-h[i].fi,x=bl[h[i].se],y=bl[h[i].se-1];if(siz[x]>siz[y]) swap(x,y);//printf("val:%d merge:%d %d %d %d %d %d\n",len,x,lp[x],rp[x],y,lp[y],rp[y]);ans+=(ull)f[len]*calc(x,y);merge(x,y);}printf("%llu",ans);
}int main()
{
#ifndef ONLINE_JUDGEfreopen("LGP5115.in","r",stdin);freopen("LGP5115.out","w",stdout);
#endifinit();solve();return 0;
}

【参考代码2】(SAM+边分树合并)

#include<bits/stdc++.h>
#define pb push_back
using namespace std;typedef unsigned long long ull;
const int N=2e5+10,M=N<<1,INF=1e9;
int n,k1,k2;
ull ans;
char s[N];namespace SAM
{struct SAM{int rt,sz,las,mx[N],fa[N],pos[N],ch[N][26];vector<int>son[N];SAM(){rt=sz=las=1;}int extend(int x){int p,np,q,nq;p=las;las=np=++sz;mx[np]=mx[p]+1;for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;if(!p) fa[np]=1;else{q=ch[p][x];if(mx[q]==mx[p]+1) fa[np]=q;else{nq=++sz;mx[nq]=mx[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}return las;}void init(char *s,int len){for(int i=1;i<=len;++i) pos[i]=extend(s[i]-'a');for(int i=1;i<=sz;++i) son[fa[i]].pb(i);}}S,T;
}
using namespace SAM;namespace Graph
{int tot,head[M];struct Tway{int v,nex,w;}e[M<<1];void Ginit(){tot=1;};void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}void addedge(int u,int v){add(u,v);add(v,u);}
}
using namespace Graph;namespace Tree
{int beg[M],en[M];int sz,drt[M],ls[M],rs[M],val[M][22];struct node{ull sl,sr;int lc,rc;}t[M*150];void insert(int &x,int id,int pos,int d){if(!id) return; x=++sz;int tmp=val[pos][d]<=k2?val[pos][d]:0;if(beg[id]<=beg[pos] && beg[pos]<=en[id]) t[x].sl=tmp,insert(t[x].lc,ls[id],pos,d+1);elset[x].sr=tmp,insert(t[x].rc,rs[id],pos,d+1);}int merge(int x,int y,ull td){if(!x || !y) return x+y;ans+=td*(t[x].sl*t[y].sr+t[x].sr*t[y].sl);t[x].sl+=t[y].sl;t[x].sr+=t[y].sr;t[x].lc=merge(t[x].lc,t[y].lc,td);t[x].rc=merge(t[x].rc,t[y].rc,td);return x;}
}
using namespace Tree;namespace Divide
{int cnt,ind,sum,rt,rtfa,rtsz,RT;int dep[M],siz[M];deque<int>q;void rebuild(int x){val[x][0]=T.mx[x]; bool f=0;for(int i=0;i<(int)T.son[x].size();++i){val[++cnt][0]=T.mx[x];addedge(!f?x:cnt-1,cnt);addedge(cnt,T.son[x][i]);f=1;}/*for(int i=0;i<(int)T.son[x].size();++i) q.pb(T.son[x][i]);for(int u,v;q.size()>=2;){u=q.front();q.pop_front();v=q.front();q.pop_front();++cnt;val[cnt][0]=T.mx[x];addedge(u,cnt);addedge(v,cnt);q.push_back(cnt);}for(;q.size();) addedge(x,q.front()),q.pop_front();*/for(int i=0;i<(int)T.son[x].size();++i) rebuild(T.son[x][i]);}//why use deque get wrong answer? //I think they calc available contribution exactly the same,thouth the tree is difvoid dfsdep(int x,int fa,int d){dep[x]=d;beg[x]=++ind;for(int i=head[x];i;i=e[i].nex)if(e[i].v^fa) dfsdep(e[i].v,x,d+1);en[x]=ind;}void getval(int x,int fa,int tp,int d){val[x][d]=abs(tp);for(int i=head[x];i;i=e[i].nex)if(e[i].v^fa && !e[i].w) getval(e[i].v,x,min(tp,val[e[i].v][0]),d);}void getroot(int x,int fa){siz[x]=1;for(int i=head[x];i;i=e[i].nex)if(e[i].v^fa && !e[i].w) getroot(e[i].v,x),siz[x]+=siz[e[i].v];int tsz=max(siz[x],sum-siz[x]);if(tsz<rtsz) rt=x,rtfa=fa,rtsz=tsz;}void divide(int &x,int y,int d){rt=rtfa=0;rtsz=INF;getroot(y,0);if(dep[rt]<dep[rtfa]) swap(rt,rtfa),siz[rt]=sum-siz[rtfa];x=rt;int tsz=siz[x],tfa=rtfa,tsum=sum;for(int i=head[rt];i;i=e[i].nex) if(e[i].v==rtfa) e[i].w=e[i^1].w=1;getval(rt,0,-1,d);getval(rtfa,0,val[rtfa][0],d);if(tsz>1) sum=tsz,divide(ls[x],x,d+1);if(tsum-tsz>1) sum=tsum-tsz,divide(rs[x],tfa,d+1);	}void dfs(int x){for(int i=0;i<(int)S.son[x].size();++i)dfs(S.son[x][i]),drt[x]=merge(drt[x],drt[S.son[x][i]],S.mx[x]<=k1?S.mx[x]:0);}
}
using namespace Divide;int main()
{
#ifndef ONLINE_JUDGEfreopen("LGP5115.in","r",stdin);freopen("LGP5115.out","w",stdout);
#endifscanf("%s%d%d",s+1,&k1,&k2);n=strlen(s+1);S.init(s,n);reverse(s+1,s+n+1);T.init(s,n);cnt=T.sz;Ginit();rebuild(T.rt);dfsdep(T.rt,0,1);sum=cnt;divide(RT,1,1);for(int i=1;i<=n;++i) insert(drt[S.pos[i]],RT,T.pos[n-i+1],1);dfs(1);printf("%llu\n",ans);return 0;
}

http://www.ppmy.cn/news/112805.html

相关文章

hdu5115-Dire Wolf【区间dp】

正题 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid5115 题目大意 有 n n n只狼&#xff0c;击败第 i i i只狼会扣 a i a_i ai​加上于其相邻的狼的 b l b r b_lb_r bl​br​点 h p hp hp。注意该狼被击败后会使原来于其相邻的狼变的相邻。 解题思路 显然区间 d p …

HDU 5115 Dire Wolf 区间dp

Dire Wolf Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid5115 Description Dire wolves, also known as Dark wolves, are extraordinarily large and powerful wolves. Many, if not all, Dire Wolves appear to originate …

HDU - 5115 经典区间dp

题意&#xff1a;给定n个狼的攻击值ai和附加攻击值bi&#xff0c;每杀死一匹狼i&#xff0c;受到的伤害等于i的攻击值和与i相邻的狼的附加攻击值。求杀死所有的狼受到的伤害的最小值。 dp[i][j]&#xff1a;杀死区间i~j的狼受到伤害的最小值。 初始化&#xff1a; a[0]a[n1]…

洛谷P5115 : SAM + 边分治 + 虚树dp

题意 给出串 S S S&#xff0c; K 1 , K 2 K1,K2 K1,K2&#xff0c;求 ∑ 1 ≤ i < j ≤ n L C P ( i , j ) ⋅ L C S ( i , j ) ⋅ [ L C P ( i , j ) ≤ K 1 ] ⋅ [ L C S ( i , j ) ≤ K 2 ] \sum_{1 \le i < j \le n}{LCP(i,j) \cdot LCS(i,j) \cdot [LCP(i,j) \le…

HDU5115(区间dp)详解

题目大意&#xff1a;你是一个战士现在面对&#xff0c;一群狼&#xff0c;每只狼都有一定的主动攻击力和附带攻击力。你杀死一只狼。你会受到这只狼的&#xff08;主动攻击力旁边两只狼的附带攻击力&#xff09;这么多伤害~现在问你如何选择杀狼的顺序使的杀完所有狼时&#x…

5115. 删除回文子数组

给你一个整数数组 arr&#xff0c;每一次操作你都可以选择并删除它的一个 回文 子数组 arr[i], arr[i1], ..., arr[j]&#xff08; i < j&#xff09;。 注意&#xff0c;每当你删除掉一个子数组&#xff0c;右侧元素都会自行向前移动填补空位。 请你计算并返回从数组中删…

【题解】hdu5115 区间DP

题目链接 dp[i][j]表示从i杀到j所受的最小伤害 dp[i][j]min(dp[i][j],dp[i][k-1]dp[k1][j]attack[k]extre[i-1]extre[j1]) 吓到了贼NM难想 //巨难想 #include<cstdio> #include<algorithm> #define INF 0x3f3f3f3f using namespace std; int dp[210][210];//d…

【洛谷P5115】Check,Check,Check one two!(后缀数组)(并查集)

传送门 题解&#xff1a; 前前后后花了几个月的时间总算是把shadowice的比赛写到只剩一道题了&#xff0c;那道题是个很水的莫队不想写了。 然而这道题标算给了个很扯的后缀自动机上边分树合并。。。TM什么毒瘤玩意 考虑选择两个极长重复子串来计算答案&#xff0c;其实就是…