股神小D

题目大意:

给定一棵树,每一条边有$L,R$两种权值,求有多少条路径满足$max(L)leqmin(R)$。

解法$1-$点分治$+$二维数点

统计树上的路径应首先想到点分治,我们很显然可以搜出过从分治重心出发的每一条路径,对应着当前重心的每一棵子树存在的若干个区间$[L_i,R_i]$,若两个不同的子树内的区间产生贡献,即这两个点形成的路径符合要求,当且仅当$[L_1,R_1]cap[L_2,R_2] eemptyset$,换言之,当两个点形成的路径没有贡献,当且仅当$R_1<L_2$或$R_2<L_1$,那么考虑先离散化,按顺序处理每一棵子树时,用树状数组维护已插入的点中$R$值和$L$值。这样,枚举到当子树时,用之前枚举的所子树中点的数量,减去$L$值过大和$R$值过小的数量,就得到了这个点对最终答案的影响。

每一条符合路径的路径仅会被路径上等级最高的分治重心所统计。如果最高等级的分治重心有两个,那么他们之间一定会有一个等级更高的分治重心。

最终复杂度为$O(Nlog^2N)$,所以本机跑了$1.7s$考场上拿到了$91$分$hhh$。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200004
using namespace std;
int read(){
	int nm=0,fh=1; char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
int n,m,c[M<<2][2],u[M],v[M],L[M<<1],R[M<<1],G[M],cnt;
int tmp,fs[M],nt[M<<1],to[M<<1],U[M<<1],D[M<<1];
int id[M<<1],od[M<<1],sum,rt,sz[M],mxs[M];
void add(int k,int x,int kd){for(;k<=m;k+=(k&-k)) c[k][kd]+=x;}
int qry(int k,int kd){int tt=0;for(;k;k-=(k&-k)) tt+=c[k][kd]; return tt;}
LL ans;
bool cmp(int x,int y){return (od[x]+od[y]==0)?od[x]<od[y]:abs(od[x])<abs(od[y]);}
bool vis[M];
void fdrt(int x,int last){
	sz[x]=1,mxs[x]=0;
	for(int i=fs[x];~i;i=nt[i]){
		if(to[i]==last||vis[to[i]]) continue;
		fdrt(to[i],x),sz[x]+=sz[to[i]];
		mxs[x]=max(mxs[x],sz[to[i]]);
	}
	mxs[x]=max(mxs[x],sum-sz[x]);
	if(mxs[x]<mxs[rt]) rt=x;
}
void dfs(int x,int last,int minn,int maxn){
	if(minn>maxn) return; sz[x]=1;
	ans+=cnt-qry(m-maxn,0)-qry(minn-1,1);
	for(int i=fs[x];i!=-1;i=nt[i]){
		if(vis[to[i]]||to[i]==last) continue;
		dfs(to[i],x,max(minn,L[i]),min(maxn,R[i]));
		sz[x]+=sz[to[i]];
	}
}
void ins(int x,int last,int minn,int maxn){
	if(minn>maxn) return; cnt++;
	add(m-minn+1,1,0),add(maxn,1,1);
	for(int i=fs[x];i!=-1;i=nt[i]){
		if(vis[to[i]]||to[i]==last) continue;
		ins(to[i],x,max(minn,L[i]),min(maxn,R[i]));
	}
}
void del(int x,int last,int minn,int maxn){
	if(minn>maxn) return;
	add(m-minn+1,-1,0),add(maxn,-1,1);
	for(int i=fs[x];i!=-1;i=nt[i]){
		if(vis[to[i]]||to[i]==last) continue;
		del(to[i],x,max(minn,L[i]),min(maxn,R[i]));
	}
}
void solve(int x){
	vis[x]=true,cnt=1;
	for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dfs(to[i],x,L[i],R[i]),ins(to[i],x,L[i],R[i]);
	for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) del(to[i],x,L[i],R[i]);
	for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) sum=sz[to[i]],rt=0,fdrt(to[i],x),solve(rt);
}
int main(){
	n=read(),sum=n,m=(n<<1)-2,memset(fs,-1,sizeof(fs)),mxs[0]=n+1;
	for(int i=1;i<n;i++){
	    u[i]=read(),v[i]=read(),D[i]=read(),U[i]=read();
	    id[(i<<1)-1]=(i<<1)-1,id[i<<1]=(i<<1);
	    od[(i<<1)-1]=-D[i],od[i<<1]=U[i];
	}
	sort(id+1,id+m+1,cmp);
	for(int i=1;i<=(n<<1);i++){
		if(id[i]&1) D[(id[i]+1)>>1]=i;
		else U[id[i]>>1]=i;
	}
	for(int i=1;i<n;++i){
		nt[tmp]=fs[u[i]],fs[u[i]]=tmp,to[tmp]=v[i],L[tmp]=D[i],R[tmp++]=U[i];
		nt[tmp]=fs[v[i]],fs[v[i]]=tmp,to[tmp]=u[i],L[tmp]=D[i],R[tmp++]=U[i];
	}
	fdrt(1,0),solve(rt),printf("%lld
",ans); return 0;
}

解法$2-$动态树

动态树的解法非常巧妙,把每一条$(u,v,L,R)$的边看做一次$(u,v,L)$的$Link$和$(u,v,R)$的$Cut$,这样,我们先把每一条边拆成两条,按照权值对操作排序,贡献值和恰好就是每次$Link(u,v)$时$u,v$所在的两个连通块点数的乘积,每条合法的路径一定会被路径上的$max(L)$的$Link$操作所统计。

复杂度为常规$LCT$的$O(Nlog N)$

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200020
#define ls c[x][0]
#define rs c[x][1]
using namespace std;
int read(){
	int nm=0,fh=1; char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
LL ans;
int n,m,fa[M],c[M][2],sz[M],tk[M],rev[M],vm[M],S[M],top;
void pushdown(int x){if(rev[x]) swap(ls,rs),rev[ls]^=1,rev[rs]^=1,rev[x]=0;}
void pushup(int x){sz[x]=sz[ls]+sz[rs]+vm[x]+1;}
bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;}
void rotate(int x){
	int tp=fa[x],dtp=fa[fa[x]],ms,ds;
	if(!isroot(tp)){if(c[dtp][0]==tp) c[dtp][0]=x;else c[dtp][1]=x;}
	if(c[tp][0]==x) ds=1,ms=0; else ds=0,ms=1;
	fa[x]=dtp,fa[tp]=x,fa[c[x][ds]]=tp;
	c[tp][ms]=c[x][ds],c[x][ds]=tp,pushup(tp),pushup(x);
}
void splay(int x){
	S[top=1]=x;
	for(int y=x;!isroot(y);y=fa[y]) S[++top]=fa[y];
	while(top) pushdown(S[top]),top--;
	while(!isroot(x)){
		int tp=fa[x];
		if(isroot(tp)) return rotate(x);
		else if(c[c[fa[tp]][0]][0]==x) rotate(tp);
		else if(c[c[fa[tp]][1]][1]==x) rotate(tp);
		else rotate(x);
	}
}
void access(int x){for(int y=0;x;y=x,x=fa[x]) splay(x),vm[x]+=sz[rs]-sz[y],rs=y,pushup(x);}
void chroot(int x){access(x),splay(x),rev[x]^=1;}
void cut(int x,int y){chroot(y),access(x),splay(x),ls=fa[y]=0,pushup(x);}
void link(int x,int y){chroot(x),chroot(y),ans+=(LL)sz[x]*(LL)sz[y],fa[x]=y,vm[y]+=sz[x],pushup(y);}
struct opt{
	int Typ,X,Y,G; opt(){}
	opt(int _Typ,int _X,int _Y,int _G){Typ=_Typ,X=_X,Y=_Y,G=_G;}
	void ect(){if(Typ==0) link(X,Y); else cut(X,Y);}
}q[M<<1];
bool cmp(opt i,opt j){return i.G==j.G?i.Typ<j.Typ:i.G<j.G;}
int main(){
	n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read(),D=read(),U=read();
		m++,q[m]=opt(0,u,v,D),m++,q[m]=opt(1,u,v,U);
	} sort(q+1,q+m+1,cmp);
	for(int i=1;i<=n;i++) sz[i]=1;
	for(int i=1;i<=m;i++) q[i].ect();
	printf("%lld
",ans);return 0;
}
原文地址:https://www.cnblogs.com/OYJason/p/9693150.html