• 虚树复习


    我记得我学过虚树啊 除了做过的题目有提交的痕迹 脑子空空如也。

    今天一定要复习好虚树 我没剩多少时间了。

    1.虚树是干嘛的?

    对于一道题目 我们发现其每次询问树上的一些点集的某种定义下的答案 通常我们不需要再次遍历整棵树来寻找答案 可以利用题目中给出的这些点集建立一颗不存在的树 即称虚树。

    2.如何构建
    我们可以发现 如果单纯的把点插到一颗不存在的树上很好插入 但是我们不一定能维护好类似于原本树的形态。所以这个时候我们需要借助两两点之间的LCA来帮忙构建这棵树 维护好这棵树的基本形态。

    3.复杂度的保证:由于是询问中的点集 我们可以将其按原本的dfs序排序 然后按顺序插入到我们的虚树当中去 并两两点求一波LCA
    可以发现 LCA可以O(1)也可以logn 由于时间复杂度的瓶颈在于排序 所以总复杂度为点集大小K*logn.

    4.应用
    大多数题目都是让我们构建出虚树然后在虚树上搞点事情 如树形dp 点分治 线段树优化建图什么的。
    总之这个技巧还是挺重要的。

    虚树构建的过程:

    虚树的构建依赖于单调栈,我们利用单调栈来维护 根到某个节点的一条笔直的链 这样我们就可以把整个基础的树的形态给完整的演绎出来。

    想着 单调栈维护的是什么东西+画图思考 就可以得到一个虚树的构建过程了 一般这样的话就不容易写错了。
    为什么要在弹栈的时候建边? 由于有些点深度小但是是后来以LCA的形式加入的 如果在某个点一开始就加入那么显然后面来一个LCA的话建边会错乱。

    由于两两点之间的LCA只有一个 所以我们构建的虚树的空间复杂度也是询问点点集的复杂度。
    code:

    inline void insert(int x)
    {
    	if(s[top]==1){s[++top]=x;return;}
    	int lca=LCA(s[top],x);
    	if(s[top]==lca){s[++top]=x;return;}
    	while(top>1&&dfn[s[top-1]]>=dfn[lca])
    	{
    		ADD(s[top-1],s[top]);
    		--top;
    	}
    	if(s[top]!=lca)
    	{
    		ADD(lca,s[top]);
    		s[top]=lca;
    	}
    	s[++top]=x;
    }
    int main()
    {
    	sort(w+1,w+1+n,cmp);
    	s[top=1]=1;
    	rep(1,n,i)if(w[i]!=1)insert(w[i]);
    	while(top>1)ADD(s[top-1],s[top]),--top;
    }
    

    值得注意的是建的边可以是单向的 如果双向边没啥大用的话...

    LINK:luogu2495SDOI2011消耗战
    这道题目 初看是一道比较裸的树形dp 但是每次给出了关键点了 所以我们直接构建虚树在虚树上dp即可。
    值得一提的是我们很容易发现 两个点同时为关键点且拥有祖先关系 那么 其中的一个点可以不要 因为祖先是一定要被割断的 割断的同时儿子也一定被隔断了...

    所以我们在构建虚树的时候 可以把s[top]==lca时 不把x加入栈中 因为s[top]此时一定是关键点 且s[top]还是x的祖先。

    估计是边权范围假了 INF 1e9 wa了

    const int MAXN=250010;
    int n,m,k,len,T,cnt,top;
    int fa[MAXN][20],d[MAXN],dfn[MAXN],s[MAXN],Log[MAXN],a[MAXN];
    int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
    ll f[MAXN],mx[MAXN],e[MAXN<<1];vector<int>g[MAXN];
    inline int cmp(int a,int b){return dfn[a]<dfn[b];}
    inline void add(int x,int y,int z)
    {
        ver[++len]=y;
        nex[len]=lin[x];
        lin[x]=len;
        e[len]=z;
    }
    inline void dfs(int x,int father)
    {
    	d[x]=d[father]+1;
    	fa[x][0]=father;dfn[x]=++cnt;
    	rep(1,Log[d[x]],i)fa[x][i]=fa[fa[x][i-1]][i-1];
    	for(int i=lin[x];i;i=nex[i])
    	{
    		int tn=ver[i];
    		if(tn==father)continue;
    		mx[tn]=min(mx[x],e[i]);
    		dfs(tn,x);
    	}
    }
    inline void add(int x,int y){g[x].push_back(y);}
    inline int LCA(int x,int y)
    {
    	if(d[x]<d[y])swap(x,y);
    	for(int i=Log[d[x]];i>=0;--i)
    		if(d[fa[x][i]]>=d[y])x=fa[x][i];
    	if(x==y)return x;
    	for(int i=Log[d[x]];i>=0;--i)
    		if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    inline void insert(int x)
    {
    	if(top==1){s[++top]=x;return;}
    	int lca=LCA(s[top],x);
    	if(lca==s[top])return;
    	while(top>1&&dfn[s[top-1]]>=dfn[lca])
    	{
    		add(s[top-1],s[top]);
    		--top;
    	}
    	if(s[top]!=lca)
    	{
    		add(lca,s[top]);
    		s[top]=lca;
    	}
    	s[++top]=x;
    }
    inline void dp(int x)
    {
    	f[x]=mx[x];
    	if(!g[x].size())return;
    	ll sum=0;
    	for(unsigned int i=0;i<g[x].size();++i)
    	{
    		int tn=g[x][i];
    		dp(tn);
    		sum+=f[tn];
    	}
    	f[x]=min(f[x],sum);
    	g[x].clear();
    }
    int main()
    {
    	freopen("1.in","r",stdin);
    	get(n);
    	rep(2,n,i)
    	{
    		int x,y,z;
    		get(x);get(y);get(z);
    		add(x,y,z);add(y,x,z);
    		Log[i]=Log[i>>1]+1;
    	}
    	mx[1]=INF;dfs(1,0);
    	get(m);
    	rep(1,m,i)
    	{
    		int k;get(k);s[top=1]=1;
    		rep(1,k,j)get(a[j]);
    		sort(a+1,a+1+k,cmp);
    		rep(1,k,j)insert(a[j]);
    		while(top>1)add(s[top-1],s[top]),--top;
    		dp(1);printf("%lld
    ",f[1]);
    	}
    	return 0;
    }
    

    这道虚树的模板题写起来挺舒服的。。明天一定要把世界树给肝出来!

    LINK:luogu P3233HNOI2014世界树

    一棵树 给出m个关键点 某个点被最近的关键点管辖 距离一样编号最小的优先。求每个关键点管辖的点的个数。

    所有点都是关键点,那当然每个点被自己管辖了...考虑怎么做 我们可以进行暴力的树形dp 但是很麻烦 直接bfs即可。。

    考虑优化这个过程 毕竟是存在优化的空间的 我们建立出来虚树。

    可以发现如果我们面对两个点都是关键点 我们可以求出一个mid来割分 但是可能不存在这种情况 但是可以逐边进行考虑。

    每一条边都有分属关系 可能被割开 也可能全部归属于某个点 而每条边都有归属 我们可以先dp一下求出归属关系。

    一条边的两个端点的归属关系可能不同 这个时候我们进行割分 相同的话显然直接决定归属。

    割分 我们直接算出mid 倍增可以统计虚边上的贡献 考虑一下除了虚边上的点 还有自己本身连接的点 这些点 对于一个点其实是其子树大小-自己下方的关键点所属链的第一个儿子 也可以倍增求。至此可以发现问题就解决了 关键是发现我们可以逐边考虑。

    存在坑点 就是找mid的时候 要用原来整条链的深度拿出来 然后除以2特判一下 倍增向上跳即可。

    这道题的关键还是要想到建出虚树之后逐边考虑计算贡献。。

    //#include<bitsstdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<ctime>
    #include<cmath>
    #include<cctype>
    #include<cstdlib>
    #include<queue>
    #include<deque>
    #include<stack>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<bitset>
    #include<set>
    #include<map>
    #define ll long long
    #define db double
    #define INF 100000000000000ll
    #define ld long double
    #define pb push_back
    #define put(x) printf("%d
    ",x)
    #define rep(p,n,i) for(RE int i=p;i<=n;++i)
    #define pii pair<ll,ll> 
    #define F first
    #define mk make_pair
    #define mod 64123
    #define RE register
    #define get(x) x=read()
    #define gt(x) scanf("%d",&x)
    #define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
    using namespace std;
    char buf[1<<15],*fs,*ft;
    inline char getc()
    {
    	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
    }
    inline int read()
    {
    	RE int x=0,f=1;char ch=getc();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    	return x*f;
    }
    const int MAXN=300010;
    int n,cnt,m,len,top;
    int a[MAXN],dfn[MAXN],s[MAXN],b[MAXN],vis[MAXN],w[MAXN];
    int Log[MAXN],f[MAXN][20],d[MAXN],sz[MAXN],c[MAXN];
    int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],ans[MAXN];
    inline int cmp(int x,int y){return dfn[x]<dfn[y];}
    inline void add(int x,int y)
    {
    	ver[++len]=y;
    	nex[len]=lin[x];
    	lin[x]=len;
    }
    inline void dfs(int x,int father)
    {
    	f[x][0]=father;d[x]=d[father]+1;dfn[x]=++cnt;
    	for(int i=1;i<=Log[d[x]];++i)f[x][i]=f[f[x][i-1]][i-1];
    	sz[x]=1;go(x)if(tn!=father)dfs(tn,x),sz[x]+=sz[tn];
    }
    inline int LCA(int x,int y)
    {
    	if(d[x]<d[y])swap(x,y);
    	for(int i=Log[d[x]];i>=0;--i)
    		if(d[f[x][i]]>=d[y])x=f[x][i];
    	if(x==y)return x;
    	for(int i=Log[d[x]];i>=0;--i)
    		if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    	return f[x][0];
    }
    inline void insert(int x)
    {
    	if(top<=1){s[++top]=x;return;}
    	int lca=LCA(s[top],x);
    	while(top>1&&dfn[s[top-1]]>=dfn[lca])
    	{
    		add(s[top-1],s[top]);
    		--top;
    	}
    	if(s[top]!=lca)add(lca,s[top]),s[top]=lca;
    	s[++top]=x;
    }
    inline int dis(int x,int y){return d[x]+d[y]-d[LCA(x,y)]*2;}
    inline void dfs1(int x)
    {
    	b[x]=vis[x]?x:0;
    	go(x)
    	{
    		dfs1(tn);
    		if(!b[x])b[x]=b[tn];
    		else
    		{
    			int dis1=dis(b[x],x);
    			int dis2=dis(b[tn],x);
    			if(dis2<dis1||dis2==dis1&&b[tn]<b[x])b[x]=b[tn];
    		}
    	}
    }
    inline void dfs2(int x)
    {
    	go(x)
    	{
    		int dis1=dis(b[x],tn);
    		int dis2=dis(b[tn],tn);
    		if(dis1<dis2||dis2==dis1&&b[x]<b[tn])b[tn]=b[x];
    		dfs2(tn);
    	}
    }
    inline void dp(int x)
    {
    	w[x]=sz[x];
    	go(x)//处理每一条虚边
    	{
    		dp(tn);
    		int v=tn,mid=tn;//分别为中点和向上的第一个点
    		for(int j=Log[d[tn]];j>=0;--j)if(d[f[v][j]]>d[x])v=f[v][j];
    		w[x]-=sz[v];
    		if(b[x]==b[tn]){ans[b[x]]+=sz[v]-sz[tn];continue;}
    		//求出分界点
    		if(v==tn)continue;
    		int midd=(d[tn]-d[x])+dis(b[tn],tn)+dis(b[x],x);
    		if(midd&1)midd=d[b[tn]]-(midd>>1);
    		else midd=d[b[tn]]-(midd>>1)+(b[x]<b[tn]);
    		for(int j=Log[d[tn]];j>=0;--j)if(d[f[mid][j]]>=midd)mid=f[mid][j];
    		ans[b[x]]+=sz[v]-sz[mid];
    		ans[b[tn]]+=sz[mid]-sz[tn];
    	}
    	ans[b[x]]+=w[x];lin[x]=0;
    }
    int main()
    {
    	freopen("1.in","r",stdin);
    	get(n);
    	rep(2,n,i)
    	{
    		int x,y;
    		get(x);get(y);
    		add(x,y);add(y,x);
    		Log[i]=Log[i>>1]+1;
    	}
    	dfs(1,0);get(m);
    	memset(lin,0,sizeof(lin));
    	rep(1,m,i)
    	{
    		int x;get(x);s[top=1]=1;len=0;
    		rep(1,x,j)c[j]=get(a[j]),vis[a[j]]=1;
    		sort(a+1,a+1+x,cmp);
    		rep(1,x,j)if(a[j]!=1)insert(a[j]);
    		while(top>1)add(s[top-1],s[top]),--top;
    		dfs1(1);dfs2(1);dp(1);
    		rep(1,x,j)printf("%d ",ans[c[j]]),ans[c[j]]=vis[c[j]]=0;
    		puts("");
    	}
    	return 0;
    }
    

    LINK:luogu 4103 HEOI2014大工程

    这道题比上一道题人性化多了。。

    给出k个点 两两之间建边 边的代价为原树上两两点之间的距离。求所有的代价总和 代价最小的边 代价最大的边。

    显然我们是不能直接(k^2)暴力的 而这道题转换到树上也不过是一个树形dp的问题考虑 设f[x]表示以x为根的子树到x的代价和,g[x]表示到x这个点的个数
    累计一下答案即可。至于第二问和第三问类似于直径和最短路径 也同样可以dp出来。

    综上我们建出虚树 在树上完成路径的dp即可。

    两问搞反了可还行,, 虚树边权打错了可还行。。。其实这道题就是普通的树形dp...谁知道我以前的代码为什么写的那么ex...

    //#include<bitsstdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<ctime>
    #include<cmath>
    #include<cctype>
    #include<cstdlib>
    #include<queue>
    #include<deque>
    #include<stack>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<bitset>
    #include<set>
    #include<map>
    #define ll long long
    #define db double
    #define INF 1000000000
    #define ld long double
    #define pb push_back
    #define put(x) printf("%d
    ",x)
    #define rep(p,n,i) for(RE int i=p;i<=n;++i)
    #define pii pair<ll,ll> 
    #define F first
    #define mk make_pair
    #define mod 64123
    #define RE register
    #define get(x) x=read()
    #define gt(x) scanf("%d",&x)
    #define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
    using namespace std;
    char buf[1<<15],*fs,*ft;
    inline char getc()
    {
    	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
    }
    inline int read()
    {
    	RE int x=0,fa=1;char ch=getc();
    	while(ch<'0'||ch>'9'){if(ch=='-')fa=-1;ch=getc();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    	return x*fa;
    }
    const int MAXN=1000010;
    int n,len,Q,top,cnt,maxx,minn;
    ll f[MAXN],w[MAXN];int g[MAXN],d1[MAXN],d2[MAXN],a[MAXN],vis[MAXN];
    int s[MAXN],d[MAXN],dfn[MAXN],fa[MAXN][21],Log[MAXN],dis[MAXN];
    int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],e[MAXN<<1];
    inline int cmp(int x,int y){return dfn[x]<dfn[y];}
    inline void add(int x,int y,int z)
    {
    	ver[++len]=y;
    	nex[len]=lin[x];
    	lin[x]=len;
    	e[len]=z;
    }
    inline void dfs(int x,int father)
    {
    	fa[x][0]=father;d[x]=d[father]+1;dfn[x]=++cnt;
    	for(int i=1;i<=Log[d[x]];++i)fa[x][i]=fa[fa[x][i-1]][i-1];
    	go(x)if(tn!=father)dfs(tn,x);
    }
    inline int LCA(int x,int y)
    {
    	if(d[x]<d[y])swap(x,y);
    	for(int i=Log[d[x]];i>=0;--i)
    		if(d[fa[x][i]]>=d[y])x=fa[x][i];
    	if(x==y)return x;
    	for(int i=Log[d[x]];i>=0;--i)
    		if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    inline void insert(int x)
    {
    	if(top<=1){s[++top]=x;return;}
    	int lca=LCA(s[top],x);
    	while(top>1&&dfn[s[top-1]]>=dfn[lca])
    	{
    		add(s[top-1],s[top],d[s[top]]-d[s[top-1]]);
    		--top;
    	}
    	if(s[top]!=lca)add(lca,s[top],d[s[top]]-d[lca]),s[top]=lca;
    	s[++top]=x;
    }
    inline void dfs(int x)
    {
    	w[x]=f[x]=g[x]=0;dis[x]=0;
    	d1[x]=-INF;d2[x]=INF;
    	if(vis[x])g[x]=1,d2[x]=d1[x]=0;
    	go(x)
    	{
    		dis[tn]=dis[x]+e[i];
    		dfs(tn);
    		maxx=max(maxx,d1[x]+d1[tn]+e[i]);
    		minn=min(minn,d2[x]+d2[tn]+e[i]);
    		d1[x]=max(d1[x],d1[tn]+e[i]);
    		d2[x]=min(d2[x],d2[tn]+e[i]);
    		f[x]+=f[tn];
    		f[x]+=(w[tn]+(ll)g[tn]*e[i])*g[x]+w[x]*g[tn];
    		g[x]+=g[tn];w[x]+=w[tn]+(ll)g[tn]*e[i];
    		
    	}lin[x]=0;
    }
    int main()
    {
    	freopen("1.in","r",stdin);
    	get(n);
    	rep(2,n,i)
    	{
    		int x,y;
    		get(x);get(y);
    		add(x,y,0);add(y,x,0);
    		Log[i]=Log[i>>1]+1;
    	}
    	dfs(1,0);get(Q);
    	memset(lin,0,sizeof(lin));
    	rep(1,Q,k)
    	{
    		int x;get(x);s[top=1]=1;len=0;
    		rep(1,x,i)get(a[i]),vis[a[i]]=1;
    		sort(a+1,a+1+x,cmp);maxx=-INF,minn=INF;
    		rep(1,x,j)if(a[j]!=1)insert(a[j]);
    		while(top>1)add(s[top-1],s[top],d[s[top]]-d[s[top-1]]),--top;
    		dfs(1);printf("%lld %d %d
    ",f[1],minn,maxx);
    		rep(1,x,i)vis[a[i]]=0;
    	}
    	return 0;
    }
    

    上一道我一直都不想做的题目。
    LINK:luogu P4242 树上的毒瘤

    题意不再赘述 显然还是上虚树 不过统计答案就显得尤为重要 求点对之间的答案。

    难度有点大 可以dp 但是 应该也可以硬上点分治 考虑点对于点对之间的贡献 最后答案乘2即可 还有修改操作 外面套一个树剖。

    虚树+点分治+树剖码量着实有点大 我考虑一种更容易的写法。

    气死了 我写的题解和代码因为死机没了 这道题的点分治做法不讲了。

    直接换根dp即可。考虑把路径拆分 一个点对需要-cnt+1。

    暴怒写完此题。 一次AC怒切。

    //#include<bitsstdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<ctime>
    #include<cmath>
    #include<cctype>
    #include<cstdlib>
    #include<queue>
    #include<deque>
    #include<stack>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<bitset>
    #include<set>
    #include<map>
    #define ll long long
    #define db double
    #define INF 1000000000
    #define ld long double
    #define pb push_back
    #define get(x) x=read()
    #define gt(x) scanf("%d",&x)
    #define put(x) printf("%d
    ",x)
    #define rep(p,n,i) for(RE int i=p;i<=n;++i)
    #define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
    #define pii pair<int,int> 
    #define F first
    #define S second
    #define mk make_pair
    #define mod 1000000007
    #define RE register
    #define zz p<<1
    #define yy p<<1|1
    #define l(p) t[p].l
    #define r(p) t[p].r
    #define sum(p) t[p].sum
    #define tag(p) t[p].tag
    #define ls zz,l,mid
    #define rs yy,mid+1,r
    using namespace std;
    char buf[1<<15],*fs,*ft;
    inline char getc()
    {
    	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
    }
    inline int read()
    {
    	RE int x=0,f=1;char ch=getc();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    	return x*f;
    }
    const int MAXN=100010;
    int n,Q,len,cnt,cc,Top;
    int a[MAXN],son[MAXN],sz[MAXN],fa[MAXN],id[MAXN],pos[MAXN],s[MAXN];ll g[MAXN],f[MAXN];
    int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],top[MAXN],d[MAXN],dfn[MAXN],vis[MAXN],e[MAXN<<1];
    struct wy
    {
    	int l,r;
    	int sum;
    	int tag;
    	inline wy(int a=0,int b=0,int c=0,int d=0){l=a;r=b;sum=c;tag=d;}
    	inline wy friend operator +(wy a,wy b){return wy(a.l,b.r,a.sum+b.sum-(a.r==b.l));} 
    }t[MAXN<<2];
    inline int cmp(int x,int y){return dfn[x]<dfn[y];}
    inline void add(int x,int y,int z=0)
    {
    	ver[++len]=y;
    	nex[len]=lin[x];
    	lin[x]=len;
    	e[len]=z;
    }
    inline void dfs(int x,int father)
    {
    	fa[x]=father;sz[x]=1;
    	d[x]=d[father]+1;dfn[x]=++cc;
    	go(x)
    	{
    		if(tn==father)continue;
    		dfs(tn,x);
    		sz[x]+=sz[tn];
    		if(sz[tn]>sz[son[x]])son[x]=tn;
    	}
    }
    inline void dp(int x,int father)
    {
    	top[x]=father;
    	id[x]=++cnt;pos[cnt]=x;
    	if(!son[x])return;
    	dp(son[x],father);
    	go(x)if(tn!=son[x]&&tn!=fa[x])dp(tn,tn);
    }
    inline void build(int p,int l,int r)
    {
    	if(l==r){l(p)=r(p)=a[pos[l]];sum(p)=1;return;}
    	int mid=(l+r)>>1;
    	build(ls);build(rs);
    	t[p]=t[zz]+t[yy];
    }
    inline void pushdown(int p)
    {
    	r(zz)=l(zz)=tag(p);
    	r(yy)=l(yy)=tag(p);
    	tag(zz)=tag(yy)=tag(p);
    	sum(zz)=sum(yy)=1;
    	tag(p)=0;return;
    }
    inline void change(int p,int l,int r,int L,int R,int x)
    {
    	if(L<=l&&R>=r)
    	{
    		sum(p)=1;
    		l(p)=r(p)=x;
    		tag(p)=x;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(tag(p))pushdown(p);
    	if(L<=mid)change(ls,L,R,x);
    	if(R>mid)change(rs,L,R,x);
    	t[p]=t[zz]+t[yy];
    }
    inline void Tchange(int x,int y,int z)
    {
    	int fx=top[x],fy=top[y];
    	while(fx!=fy)
    	{
    		if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
    		change(1,1,n,id[fx],id[x],z);
    		x=fa[fx];fx=top[x];
    	}
    	if(d[x]<d[y])swap(x,y);
    	change(1,1,n,id[y],id[x],z);
    }
    inline int LCA(int x,int y)
    {
    	int fx=top[x],fy=top[y];
    	while(fx!=fy)
    	{
    		if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
    		x=fa[fx];fx=top[x];
    	}
    	if(d[x]<d[y])swap(x,y);
    	return y;
    }
    inline wy ask(int p,int l,int r,int L,int R)
    {
    	if(L<=l&&R>=r)return t[p];
    	int mid=(l+r)>>1;
    	if(tag(p))pushdown(p);
    	if(R<=mid)return ask(ls,L,R);
    	if(L>mid)return ask(rs,L,R);
    	return ask(ls,L,R)+ask(rs,L,R);
    }
    inline int Task(int x,int y)
    {
    	wy b;
    	//cout<<x<<' '<<y<<' ';
    	int fx=top[x],fy=top[y];
    	while(fx!=fy)
    	{
    		if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
    		b=ask(1,1,n,id[fx],id[x])+b;
    		x=fa[fx];fx=top[x];
    	}
    	if(d[x]<d[y])swap(x,y);
    	b=ask(1,1,n,id[y],id[x])+b;
    	//cout<<b.sum<<endl;
    	return b.sum;
    }
    inline void insert(int x)
    {
        if(Top<=1){s[++Top]=x;return;}
        int lca=LCA(s[Top],x);
        while(Top>1&&dfn[s[Top-1]]>=dfn[lca])
        {
            add(s[Top-1],s[Top],Task(s[Top-1],s[Top])-1);
            --Top;
        }
        if(s[Top]!=lca)add(lca,s[Top],Task(lca,s[Top])-1),s[Top]=lca;
        s[++Top]=x;
    }
    inline void dp(int x)
    {
    	g[x]=vis[x];f[x]=0;
    	go(x)
    	{
    		dp(tn);
    		f[x]+=g[tn]*e[i];
    		f[x]+=f[tn];
    		g[x]+=g[tn];
    	}
    }
    inline void dfs(int x)
    {
    	go(x)
    	{
    		f[tn]=f[x]-g[tn]*e[i]+(g[x]-g[tn])*e[i];
    		g[tn]=g[x];
    		dfs(tn);
    	}lin[x]=0;
    }
    int main()
    {
    	freopen("1.in","r",stdin);
    	get(n);get(Q);
    	rep(1,n,i)get(a[i]);
    	rep(1,n-1,i){int x,y;get(x);get(y);add(x,y);add(y,x);}
    	dfs(1,0);dp(1,1);build(1,1,n);
    	memset(lin,0,sizeof(lin));
    	rep(1,Q,i)
    	{
    		int op,x;len=0;
    		get(op);get(x);
    		if(op==1)
    		{
    			int y;get(y);
    			Tchange(x,y,read());
    		}
    		if(op==2)
    		{
    			rep(1,x,j)sz[j]=get(a[j]),vis[a[j]]=1;
    			sort(a+1,a+1+x,cmp);
    			s[Top=1]=1;
    			rep(1,x,j)if(a[j]!=1)insert(a[j]);
    			while(Top>1)add(s[Top-1],s[Top],Task(s[Top-1],s[Top])-1),--Top;
    			dp(1);dfs(1);rep(1,x,j)printf("%lld ",f[sz[j]]+x),vis[sz[j]]=0;
    			puts("");
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    得不到的永远在骚动
    这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧【转】
    Windows CMD命令大全【转】
    创业为什么选择北上广?
    为什么你还把时间浪费在无效人脉社交上?
    五步搞定Android开发环境部署——非常详细的Android开发环境搭建教程
    mysql分区操作
    程序员常用网站
    4种提升SQL查询性能的知识
    获得局域网内其他成员的信息
  • 原文地址:https://www.cnblogs.com/chdy/p/12489914.html
Copyright © 2020-2023  润新知