• 2014多校6 Another Letter Tree


    2014多校6 Another Letter Tree

    4种做法略解

    点分治做法

    就裸地离个线,放到点分治上,从每个根开始,维护(dp_{u,l,r})表示这条链匹配了序列中([l,r])的部分

    注意dp数组要一正一反,俩家伙一个含根一个不含

    查询要合并两个dp数组,但是只需要知道(dp_{1,|s_0|}),因此合并复杂度是(O(|s_0|))

    最终复杂度,处理为(O(nlog n|s_0|^2+q|s_0|))

    树剖线段树做法

    类似上面的dp,线段树维护即可

    问题1

    需要存正反!

    然后你发现内存从中间裂开!!

    正反分两次,离线跑两次就可以了啊啊啊啊

    问题2

    如果直接查询合并,合并两个dp数组复杂度为(O(|s_0|^3))

    查询复杂度为(O(qlog ^2n|s_0|^3))

    妙啊!!比(n^2)还大!!

    所以最后不能合并dp数组,而应该直接累加到答案数组上

    问题3

    没错现在我们的复杂度为(O(qlog ^2n|s_0|^2))

    依然大得令人无法忍受

    但是没想到吧,数据全部都是链,树剖是(O(1))

    优化:查询重链时,只有最后依次是在链上查询([l,r])都在中间的,而对于直接跳到top的部分,可以预处理出来

    算上线段树的预处理,这样总复杂度就是(O(n|s_0|^3+qlog n |s_0|^2))

    并查集做法

    把问题拆成查询两条(u)到它的祖先(v)的答案

    每个节点存储一个dp矩阵,用带权并查集维护

    具体方法是:将询问按照( ext{LCA})深度逆序排序后,每次查询一直将(u)合并到(v)为止

    复杂度为(O(nalpha(n)|s_0|^3)),理论上来说,这个转移矩阵应当很稀疏,乘法应该很快,但是实际常数比较大

    #include<bits/stdc++.h>
    using namespace std;
    #pragma GCC optimize(2)
    #define reg register
    #define Mod1(x) ((x>=P)&&(x-=P))
    #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
    char IO;
    int rd(){
    	int s=0;
    	while(!isdigit(IO=getchar()));
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return s;
    }
    
    const int N=5e4+10,P=10007;
    
    int n,m,len;
    char str[N],t[N];
    struct Node{
    	int a[31][31];
    	void clear(){ memset(a,0,sizeof a); }
    	void Init(int u) { clear(); rep(i,1,len) if(t[i]==str[u]) a[i][i]=1; }
    	Node operator + (const Node &x) const {
    		Node res; res.clear();
    		rep(i,1,len) for(int j=i;j<len && a[i][j];++j) for(int k=j+1;k<=len && x.a[j+1][k];++k) res.a[i][k]=(res.a[i][k]+a[i][j]*x.a[j+1][k])%P;
    		rep(i,1,len) rep(j,i,len) {
    			res.a[i][j]+=a[i][j],Mod1(res.a[i][j]);
    			res.a[i][j]+=x.a[i][j],Mod1(res.a[i][j]);
    		}
    		return res;
    	}
    } s[N];
    
    struct Edge{
    	int to,nxt;
    }e[N<<1];
    int head[N],ecnt;
    void AddEdge(int u,int v) {
    	e[++ecnt]=(Edge){v,head[u]};
    	head[u]=ecnt; 
    }
    #define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
    
    int QX[N],QY[N],QL[N];
    int dep[N],id[N],fa[N][18];
    void pre_dfs(int u,int f) {
    	dep[u]=dep[fa[u][0]=f]+1;
    	rep(i,1,17) fa[u][i]=fa[fa[u][i-1]][i-1];
    	erep(u,i) {
    		int v=e[i].to;
    		if(v==f) continue;
    		pre_dfs(v,u);
    	}
    }
    int LCA(int x,int y) {
    	if(dep[x]<dep[y]) swap(x,y);
    	for(int i=0,del=dep[x]-dep[y];(1<<i)<=del;++i) if(del&(1<<i)) x=fa[x][i];
    	if(x==y) return x;
    	drep(i,17,0) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    
    int F[N],ans[N][31],Ans[N];
    int Find(int x,int k=0) {
    	if(F[x]==x) return x;
    	int f=F[x]; F[x]=Find(f,k);
    	if(F[f]!=f){
    		if(!k) s[x]=s[x]+s[f];
    		else s[x]=s[f]+s[x];
    	}
    	return F[x];
    }
    
    int main(){
    	rep(kase,1,rd()) {
    		n=rd(),m=rd();
    		rep(i,1,n) head[i]=ecnt=0;
    		rep(i,2,n) {
    			int u=rd(),v=rd();
    			AddEdge(u,v),AddEdge(v,u);
    		}
    		scanf("%s%s",str+1,t+1),len=strlen(t+1);
    		pre_dfs(1,0);
    		rep(i,1,m) id[i]=i,QX[i]=rd(),QY[i]=rd(),QL[i]=LCA(QX[i],QY[i]);
    		sort(id+1,id+m+1,[&](int x,int y){ return dep[QL[x]]>dep[QL[y]]; });
    
    		rep(i,1,n) F[i]=i,s[i].clear();
    		rep(k,1,m){
    			int i=id[k],x=QX[i];
    			while(1){
    				int y=Find(x);
    				if(y==QL[i]) break;
    				F[y]=fa[y][0],s[y].Init(y);
    			}
    			ans[i][0]=1;
    			rep(j,1,len) ans[i][j]=s[x].a[1][j];
    			drep(j,len,1) if(str[QL[i]]==t[j]) ans[i][j]+=ans[i][j-1],Mod1(ans[i][j]);
    		}
    
    		rep(i,1,n) F[i]=i,s[i].clear();
    		rep(k,1,m){
    			int i=id[k],x=QY[i]; Ans[i]=0;
    			while(1){
    				int y=Find(x,1);
    				if(y==QL[i]) break;
    				F[y]=fa[y][0],s[y].Init(y);
    			}
    			rep(j,0,len-1) Ans[i]=(Ans[i]+ans[i][j]*s[x].a[j+1][len])%P;
    			Ans[i]=(Ans[i]+ans[i][len])%P;
    		}
    		rep(i,1,m) printf("%d
    ",Ans[i]);
    	}
    }
    
    

    [ ]

    伪矩阵求逆做法

    同样的,把问题拆成查询两条(u)到它的祖先(v)的答案(不包含v)

    以从(v)(u)的字符串为例,设(dp_u)(u)的祖先链的dp矩阵,我们要求的部分答案是(x)

    (dp_vcdot x=dp_u, x=frac{dp_u}{dp_v})

    一般来说,矩阵求逆是一个很难实现的东西

    但是发现对于一种(dp),它的矩阵一定是一个上/下对角的矩阵

    我们需要求出矩阵第一维为1或者第二维为(|s_0|)的部分

    如果暴力求,可以看做求解一个(|s_0|)元的线性方程组,可以用高斯消元在(O(|s_0|^3))时间内求解

    而实际上,这个线性方程组是含拓扑序关系的,任何一个含拓扑关系的线性方程组求解是不需要高斯消元的

    而且这个问题列出的方程矩阵就已经是上对角矩阵了

    所以写出来就是容斥吧

    tips: 预处理部分一次只插入一个字符,复杂度为(O(n|s_0|^2)) (也可以认为是稀疏矩阵乘法)

    查询部分求解线性方程复杂度为(O(|s_0|^2)),合并答案复杂度为(O(|s_0|))

    因此复杂度为(O((n+q)|s_0|^2))

    Code: 注意两种dp共用了一个数组

    #include<bits/stdc++.h>
    using namespace std;
    #define reg register
    #define Mod1(x) ((x>=P)&&(x-=P))
    #define Mod2(x) ((x<0)&&(x+=P))
    #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
    char IO;
    int rd(){
    	int s=0;
    	while(!isdigit(IO=getchar()));
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return s;
    }
    
    const int N=5e4+10,P=10007;
    
    int n,m,len;
    char str[N],t[N];
    struct Edge{
    	int to,nxt;
    }e[N<<1];
    int head[N],ecnt;
    void AddEdge(int u,int v) {
    	e[++ecnt]=(Edge){v,head[u]};
    	head[u]=ecnt; 
    }
    #define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
    
    int dep[N],id[N],fa[N][18];
    int dp[N][31][31];
    int f1[31],f2[31];
    
    void pre_dfs(int u,int f) {
    	dep[u]=dep[fa[u][0]=f]+1;
    	rep(i,1,17) fa[u][i]=fa[fa[u][i-1]][i-1];
    	rep(i,1,len) rep(j,1,len) {
    		dp[u][i][j]=dp[f][i][j];
    		if(str[u]==t[j]) {
    			if(i==j) dp[u][i][j]++,Mod1(dp[u][i][j]);
    			if(i<j) dp[u][i][j]+=dp[f][i][j-1],Mod1(dp[u][i][j]);
    			if(i>j) dp[u][i][j]+=dp[f][i][j+1],Mod1(dp[u][i][j]);
    		}
    	}
    	erep(u,i) {
    		int v=e[i].to;
    		if(v==f) continue;
    		pre_dfs(v,u);
    	}
    }
    int LCA(int x,int y) {
    	if(dep[x]<dep[y]) swap(x,y);
    	for(int i=0,d=dep[x]-dep[y];(1<<i)<=d;++i) if(d&(1<<i)) x=fa[x][i];
    	if(x==y) return x;
    	drep(i,17,0) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    
    void Calcdp1(int u,int f) {
    	rep(i,f1[0]=1,len) {
    		f1[i]=dp[u][i][1];
    		rep(j,0,i-1) f1[i]=(f1[i]-f1[j]*dp[f][i][j+1])%P;
    		Mod2(f1[i]);
    	}
    }
    
    void Calcdp2(int u,int f) {
    	drep(i,len,f2[len+1]=1) {
    		f2[i]=dp[u][i][len];
    		rep(j,i+1,len+1) f2[i]=(f2[i]-dp[f][i][j-1]*f2[j])%P;
    		Mod2(f2[i]);
    	}
    }
    int Que(int x,int y) {
    	int lca=LCA(x,y);
    	Calcdp1(x,fa[lca][0]),Calcdp2(y,lca);
    	int ans=0;
    	rep(i,0,len) ans=(ans+f1[i]*f2[i+1])%P;
    	return ans;
    }
    
    int main(){
    	rep(kase,1,rd()) {
    		n=rd(),m=rd();
    		rep(i,1,n) head[i]=ecnt=0;
    		rep(i,2,n) {
    			int u=rd(),v=rd();
    			AddEdge(u,v),AddEdge(v,u);
    		}
    		scanf("%s%s",str+1,t+1),len=strlen(t+1);
    		pre_dfs(1,0);
    		rep(i,1,m) {
    			int x=rd(),y=rd();
    			printf("%d
    ",Que(x,y));
    		}
    	}
    }
    
    
  • 相关阅读:
    XML 文档(1, 2)中有错误:不应有 <xml xmlns=''>
    工厂模式、控制反转及依赖注入
    [译]新的CCSDS图像压缩推荐标准
    关于C#的静态类和静态构造函数
    c++友元函数与友元类
    malloc/free 与 new/delete 比较
    C++ main函数命令行参数使用
    进程和线程的区别
    堆和栈的区别
    java与C++的区别
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13588044.html
Copyright © 2020-2023  润新知