• CF1073G Yet Another LCP Problem


    XV.CF1073G Yet Another LCP Problem

    这里记录一下我在思考本题时的一个感悟,即后缀数组与后缀自动机的等价性。

    众所周知,SA时有一个常见思路就是针对 height 数组建一棵笛卡尔树。但是,该笛卡尔树,唯一等价于SA针对的串的反串的parent tree。具体可以分别考虑SA上笛卡尔树和parent tree的实际含义。

    这也就意味着,二者的应用是类似的。笛卡尔树上,两条后缀的 \(\text{LCP}\) 是其 \(\text{LCA}\) 的深度;换到parent tree上也一样。

    于是现在考虑我们的询问,就会发现,其等价于在上述两棵树上,两组点两两间 \(\text{LCA}\) 的深度。

    这里我无脑就将深度差分转换成了路径加、路径求和,然后直接上了树剖维护,是 \(\log^2n\) 的。一开始T掉了,然后拿BIT换掉了线段树,加了快读快输,就卡过去了。不过,正解应该是将两个集合并一块跑虚树,然后树形DP一下关于每个点求出以其为 \(\text{LCA}\) 的点对数,乘以其深度就得到了答案,复杂度为虚树复杂度,一个 \(\log\)

    这里只有树剖的代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,id[200100],cnt=1;
    struct Suffix_Automaton{int ch[26],len,fa;}t[400100];
    int Add(int x,int c){
    	int xx=++cnt;t[xx].len=t[x].len+1;
    	for(;x&&!t[x].ch[c];x=t[x].fa)t[x].ch[c]=xx;
    	if(!x){t[xx].fa=1;return xx;}
    	int y=t[x].ch[c];
    	if(t[y].len==t[x].len+1){t[xx].fa=y;return xx;}
    	int yy=++cnt;t[yy]=t[y],t[yy].len=t[x].len+1;
    	t[xx].fa=t[y].fa=yy;
    	for(;x&&t[x].ch[c]==y;x=t[x].fa)t[x].ch[c]=yy;
    	return xx;
    }
    char s[200100];
    int fa[400100],dfn[400100],sz[400100],son[400100],rev[400100],top[400100],dep[400100],tot;
    vector<int>v[400100];
    void dfs1(int x){
    	sz[x]=1;
    	for(auto y:v[x]){
    		fa[y]=x,dep[y]=dep[x]+1;
    		dfs1(y);
    		sz[x]+=sz[y];
    		if(sz[y]>sz[son[x]])son[x]=y;
    	}
    }
    void dfs2(int x){
    	dfn[x]=++tot,rev[tot]=x;if(!top[x])top[x]=x;
    	if(son[x])top[son[x]]=top[x],dfs2(son[x]);
    	for(auto y:v[x])if(y!=son[x])dfs2(y);
    }
    #define lson x<<1
    #define rson x<<1|1
    #define mid ((l+r)>>1)
    struct BIT{//a universal template of fenwick tree
    	ll s[400100];//the prefix sum of original array
    	ll t1[400100],t2[400100];//the unweighted and weighted fenwick
    	ll&operator[](const int &x){return s[x];}
    	BIT(){memset(s,0,sizeof(s)),memset(t1,0,sizeof(t1)),memset(t2,0,sizeof(t2));}
    	void ADD(int x,int y){for(int i=x;i<=cnt;i+=i&-i)t1[i]+=y,t2[i]+=1ll*s[x-1]*y;}
    	ll SUM(int x){
    		ll sum=0;
    		for(int i=x;i;i-=i&-i)sum+=t1[i];
    		sum*=s[x];
    		for(int i=x;i;i-=i&-i)sum-=t2[i];
    		return sum;
    	}
    	void ADD(int l,int r,int val){if(l<=r)ADD(l,val),ADD(r+1,-val);}
    	ll SUM(int l,int r){return SUM(r)-SUM(l-1);}
    }bit;
    void chainadd(int x,int val){x=id[n-x];while(x)bit.ADD(dfn[top[x]],dfn[x],val),x=fa[top[x]];}
    ll chainsum(int x){x=id[n-x];ll ret=0;while(x)ret+=bit.SUM(dfn[top[x]],dfn[x]),x=fa[top[x]];return ret;}
    int a[200100];
    ll res;
    int read(){
    	int x=0;
    	char c=getchar();
    	while(c>'9'||c<'0')c=getchar();
    	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return x;
    }
    void print(ll x){
    	if(x<=9)putchar('0'+x);
    	else print(x/10),putchar('0'+x%10);
    }
    int main(){
    	n=read(),m=read(),scanf("%s",s),reverse(s,s+n);
    	for(int i=0,las=1;i<n;i++)id[i]=las=Add(las,s[i]-'a');
    	for(int i=2;i<=cnt;i++)v[t[i].fa].push_back(i);
    	dfs1(1),dfs2(1);
    //	for(int i=1;i<=cnt;i++)printf("FA:%d SN:%d DP:%d SZ:%d DF:%d RV:%d TP:%d\n",fa[i],son[i],dep[i],sz[i],dfn[i],rev[i],top[i]); 
    	for(int i=1;i<=cnt;i++)bit[i]=bit[i-1]+t[rev[i]].len-t[t[rev[i]].fa].len;
    //	for(int i=1;i<=cnt;i++)printf("%d ",t[i].len);puts("");
    	for(int i=1,A,B;i<=m;i++){
    		A=read(),B=read(),res=0;
    		for(int i=1;i<=A;i++)chainadd(a[i]=read(),1);
    		for(int i=1;i<=B;i++)res+=chainsum(read());
    		for(int i=1;i<=A;i++)chainadd(a[i],-1);
    		print(res),putchar('\n');
    	}
    	return 0;
    }
    

  • 相关阅读:
    Linux源码Kconfig文件语法分析
    从0移植uboot (一) _配置分析
    ARM汇编与C混合编程
    ARM汇编程序结构
    ARMGNU伪指令
    Linux tcp黏包解决方案
    Linux 服务器模型小结
    Linux IPC udp/tcp/UNIX域 socket编程
    Linux IPC socket 广播,组播
    Linux I/O多路复用
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605733.html
Copyright © 2020-2023  润新知