洛谷3181 HAOI2016找相同字符 (SA+单调栈)

题目链接

QWQ好自闭的题目!
一个题解都看不懂!!!!
貌似这种求 a n s ans ans的代码实现是全网第一个?

QWQ
总之我没有见过类似的啊。

首先这个题,我们这么考虑。

由于是两个串,很自然的想到把第二个串拼到第一个串的后面,然后中间添加一个非法字符。

那么我们应该怎么求呢?

首先想一个复杂度不是那么优秀的做法。
我们可以直接暴力枚举任意两个后缀,一个属于A串,另一个属于B串,他们的 l c p lcp lcp就是会重复的子串个数。

但这个复杂度很明显是不能接受的。我们考虑AHOI2013差异那个题的计算方法。

在本题中,我们对于每一个 h e i g h t height height通过单调栈求出来以它为最小值的最靠左的点 l [ i ] l[i] l[i]和最靠右的点 r [ i ] r[i] r[i]。然后对于一个 h e i g h t height height它的贡献就是左边的A串后缀数乘上右边的B串后缀数+左边的B串后缀数乘上右边的A串后缀数

写成式子就是
h e i g h t [ i ] ∗ ( g e t a ( i − 1 , l [ i ] − 1 ) ∗ g e t b ( r [ i ] , i ) + g e t b ( i − 1 , l [ i ] − 1 ) ∗ g e t a ( r [ i ] , i ) ) height[i]*(geta(i-1,l[i]-1)*getb(r[i],i)+getb(i-1,l[i]-1)*geta(r[i],i)) height[i](geta(i1,l[i]1)getb(r[i],i)+getb(i1,l[i]1)geta(r[i],i))

这里之所以是这个式子的原因:第一要保证是一个端点属于A串,一个属于B串。另一个原因是因为对于一个扩展区间 [ l , p o s , r ] [l,pos,r] [l,pos,r]来说,选择后缀的右端点是在 [ p o s , r ] [pos,r] [pos,r]而左端点是 [ l − 1 , p o s − 1 ] [l-1,pos-1] [l1,pos1],因为后缀的选择的左边对于 h e i g h t height height是开区间(求两个后缀的 l c p lcp lcp ,应该是 m i n ( h e i g h t [ r k [ i ] + 1 ] , h e i g h t [ r k [ i ] + 2 ] . . . . . h e i g h t [ r k [ j ] ] ) min(height[rk[i]+1],height[rk[i]+2].....height[rk[j]]) min(height[rk[i]+1],height[rk[i]+2].....height[rk[j]])

总之细节很多

我果然是好菜好菜

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
   return x*f;
}
const int maxn = 2e6+1e2;
struct Node{
	int val,pos;
};
int wb[maxn],sa[maxn];
int tmp[maxn],rk[maxn];
char a[maxn];
int n,m;
int h[maxn],height[maxn];
int len,len1;
char s[maxn],s1[maxn];
int l[maxn],r[maxn];
Node st[maxn];
int top;
int ans;
void getsa()
{
    int *x=rk,*y=tmp;
    int s=128;
    int p=0;
    for (int i=1;i<=n;i++) x[i]=a[i],y[i]=i;
    for (int i=1;i<=s;i++) wb[i]=0;
    for (int i=1;i<=n;i++) wb[x[y[i]]]++;
    for (int i=1;i<=s;i++) wb[i]+=wb[i-1];
    for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
    for (int j=1;p<n;j<<=1)
    {
        p=0;
        for (int i=n-j+1;i<=n;i++) y[++p]=i;
        for (int i=1;i<=n;i++) if(sa[i]>j) y[++p]=sa[i]-j;
        for (int i=1;i<=s;i++) wb[i]=0;
        for (int i=1;i<=n;i++) wb[x[y[i]]]++;
        for (int i=1;i<=s;i++) wb[i]+=wb[i-1];
        for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--] = y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for (int i=2;i<=n;i++)
        {
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p;
        }
        s=p;
    }
    for (int i=1;i<=n;i++) rk[sa[i]]=i;
    h[0]=0;
    for (int i=1;i<=n;i++)
    {
        h[i]=max(h[i-1]-1,0ll);
        while (i+h[i]<=n && sa[rk[i]-1]+h[i]<=n && a[i+h[i]]==a[sa[rk[i]-1]+h[i]]) h[i]++;
    }
    for (int i=1;i<=n;i++) height[i]=h[sa[i]]; 
}
int suma[maxn],sumb[maxn];
int geta(int r,int l)
{
	if (!l) return suma[r];
    return suma[r]-suma[l-1];
}
int getb(int r,int l)
{
	if (!l) return sumb[r];
	return sumb[r]-sumb[l-1];
}
signed main()
{
  scanf("%s",s+1);
  scanf("%s",s1+1);
  len=strlen(s+1);
  len1=strlen(s1+1);
  for (int i=1;i<=len;i++) a[++n]=s[i];
  a[++n]='*';
  for (int i=1;i<=len1;i++) a[++n]=s1[i];
  getsa();
  for (int i=1;i<=n;i++)
  {
  	if(sa[i]<=len) suma[i]++;
  	else if (sa[i]>len+1) sumb[i]++;
  }
  for (int i=1;i<=n;i++) suma[i]+=suma[i-1];
  for (int i=1;i<=n;i++) sumb[i]+=sumb[i-1];
  l[1]=1;
  top=1;
  st[top].val=height[1];
  st[top].pos=1;
  for (int i=2;i<=n;i++)
  {
  	 while (top>=1 && height[i]<=st[top].val) top--;
  	 if (!top) l[i]=1;
  	 else l[i] = st[top].pos+1;	
  	 st[++top].pos=i;
  	 st[top].val=height[i];
  }
  r[n]=n;
  top=1;
  st[top].val=height[n];
  st[top].pos=n;
  for (int i=n-1;i>=1;i--)
  {
  	while (top>=1 && height[i]<st[top].val) top--;
  	if (!top) r[i]=n;
  	else r[i]=st[top].pos-1;
  	st[++top].val=height[i];
  	st[top].pos=i;
  }
  for (int i=1;i<=n;i++) 
  {
    ans=ans+height[i]*(geta(i-1,l[i]-1)*getb(r[i],i)+getb(i-1,l[i]-1)*geta(r[i],i));
    //cout<<ans<<" "<<height[i]<<" "<<l[i]<<" "<<r[i]<<endl;
  }
  cout<<ans;
  return 0;
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值