并不对劲的loj3046:p5327:[ZJOI2019]语言

题目大意

(n)个点的树,给出(m)条关键路径。
问有多少对点满足连接它们的简单路径是至少1条关键路径的一部分。
(n,mleq 10^5)

题解

可以对于每个点,求出和它在同一条路径上的点有多少个。
也就是所有过它的关键路径的端点的虚树除它以外有多少个点。
虚树中的边数相当于虚树中dfs序相邻两点距离之和/2。点数=边数+1。
可以用线段树维护按dfs序排的虚树中的点:在每条关键路径端点的线段树中加入该关键路径的端点,在路径端点的lca处减两次该关键路径的端点;父子之间的转移可以线段树合并。

代码
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define view(u,k) for(int k=fir[u];~k;k=nxt[k])
#define maxn 100007
#define maxm 200007
#define maxnd 40000007 
#define maxdep 20
#define ls son[u][0]
#define rs son[u][1]
#define mi ((l+r)>>1)
#define LL long long
#define pii pair<int ,int >
#define fi first
#define se second
#define mp make_pair
using namespace std;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x*f;
}
void write(LL x)
{
	if(x==0){putchar('0'),putchar('
');return;}
	int f=0;char ch[20];
	if(x<0)putchar('-'),x=-x;
	while(x)ch[++f]=x%10+'0',x/=10;
	while(f)putchar(ch[f--]);
	putchar('
');
	return;
}
vector<pii >dele[maxn];
int tr[maxnd],num[maxnd],lc[maxnd],rc[maxnd],bac[maxn],rt[maxn],son[maxnd][2],n,m,fir[maxn],nxt[maxm],v[maxm],cnte,cntnd;
int ord[maxn],cnt,dfn[maxn],tim,dep[maxn],lg[maxn<<1],st[maxdep][maxn<<1],fa[maxn];
LL ans;
inline void ade(int u1,int v1){v[cnte]=v1,nxt[cnte]=fir[u1],fir[u1]=cnte++;}
void gettim(int u)
{
	ord[u]=++cnt,bac[cnt]=u,dfn[u]=++tim,st[0][tim]=u;
	view(u,k)if(v[k]!=fa[u])
	{
		fa[v[k]]=u,dep[v[k]]=dep[u]+1,gettim(v[k]);
		st[0][++tim]=u;
	}
}
inline int lca(int x,int y)
{
	x=dfn[x],y=dfn[y];if(x>y)swap(x,y);
	int len=y-x+1;
	return dep[st[lg[len]][x]]<dep[st[lg[len]][y-(1<<lg[len])+1]]?st[lg[len]][x]:st[lg[len]][y-(1<<lg[len])+1];
}
inline int dis(int x,int y)
{
	int L=lca(x,y);
	return dep[x]+dep[y]-dep[L]-dep[L];
}
void pu(int u,int l,int r)
{
	if(l==r)return;
	if(!ls||!rs){int s=(ls|rs);tr[u]=tr[s],lc[u]=lc[s],rc[u]=rc[s];}
	else tr[u]=tr[ls]+tr[rs]+dis(rc[ls],lc[rs]),lc[u]=lc[ls],rc[u]=rc[rs];
	if(l==1&&r==n)tr[u]+=dis(lc[u],rc[u]);
	return;
}
void add(int &u,int l,int r,int x)
{
	if(!u)u=++cntnd;
	if(x<=l&&r<=x){num[u]++,lc[u]=rc[u]=bac[x];return;}
	if(x<=mi)add(ls,l,mi,x);
	else add(rs,mi+1,r,x);
	pu(u,l,r);return;
}
void del(int &u,int l,int r,int x)
{
	if(!u)return;
	if(x<=l&&r<=x){num[u]-=2;if(!num[u])u=0;return;}
	if(x<=mi)del(ls,l,mi,x);
	else del(rs,mi+1,r,x);
	if(!ls&&!rs)u=0;
	else pu(u,l,r);
	return;
}
void merge(int & u,int ub,int l,int r)
{
	if(!u||!ub){u=u|ub;return;}
	if(l==r){num[u]+=num[ub],tr[u]=0,lc[u]=lc[u]|lc[ub],rc[u]=rc[u]|rc[ub];return;}
	merge(ls,son[ub][0],l,mi),merge(rs,son[ub][1],mi+1,r);
	pu(u,l,r);return;
}
void work(int u)
{
	view(u,k)if(v[k]!=fa[u]){work(v[k]);merge(rt[u],rt[v[k]],1,n);}
	if(rt[u]&&tr[rt[u]])ans+=(LL)(tr[rt[u]]>>1);
	int li=dele[u].size()-1;
	rep(i,0,li)del(rt[u],1,n,ord[dele[u][i].fi]),del(rt[u],1,n,ord[dele[u][i].se]);
}
int main()
{
	n=read(),m=read();
	rep(i,1,n)fir[i]=-1;
	rep(i,2,n){int x=read(),y=read();ade(x,y),ade(y,x);} 
	gettim(1),lg[0]=-1;
	rep(i,1,tim)lg[i]=lg[i>>1]+1;
	rep(k,1,lg[tim])for(int j=1;j+(1<<k)-1<=tim;j++)
		st[k][j]=dep[st[k-1][j]]<dep[st[k-1][j+(1<<(k-1))]]?st[k-1][j]:st[k-1][j+(1<<(k-1))];
	rep(i,1,m)
	{
		int x=read(),y=read(),z=lca(x,y);
		if(x==y)continue;
		add(rt[x],1,n,ord[x]),add(rt[x],1,n,ord[y]);
		add(rt[y],1,n,ord[x]),add(rt[y],1,n,ord[y]);
		dele[z].push_back(mp(x,y));
	}
	work(1);
	write(ans/2ll);
	return 0;
}
“数数有几个log?”

并不对劲的做法:
第一个线段树的每个节点存包含了该节点表示的整个区间的每条关键路径。
遍历第一棵树,走到一个点就把所有边加(对一个路径加,时间复杂度是(log_2space n(重链剖分) imes log_2space n(线段树)))到可持久化线段树的第当前深度个版本中(不知道区间修改能不能不可持久化),这样第二棵树的当前深度的版本中每个节点的权值是对应区间在dfs序中的被“包含了第一棵树整个当前区间的关键路径”包含的点数。
遍历到第一棵树的叶子结点时,被包含了它的边覆盖了的点。这里刚好是第二棵树根节点的权值。
问:这玩意儿有几个log?只拿了暴力的50分和链的10分。

原文地址:https://www.cnblogs.com/xzyf/p/13040199.html