洛谷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;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值