【DSU ON TREE优化空间+线段树合并】HDU5511 Minimum Cut-Cut

【题目】
HDU
给定一幅 n n n个点 m m m条边的无向图以及这个无向图的一棵生成树,非树边的 l c a lca lca均为 1 1 1
求一个最小的割满足割去恰好两条树边,多组数据。
n ≤ 2 × 1 0 4 , m ≤ 1 0 5 , T ≤ 25 n\leq 2\times 10^4,m\leq 10^5,T\leq 25 n2×104,m105,T25

【解题思路】
割树边的方式有两种情况:割了两条存在祖先关系的,或割了两条没有祖先关系的。

d x d_x dx表示一个端点落在 x x x子树内所有非树边条数。

那么对于第一种情况,设答案删的是 x x x的父边和 y y y的父边,答案显然就是 d x − d y d_x-d_y dxdy,那么 x x x固定时 y y y x x x的儿子显然是最优的。这部分直接枚举即可,复杂度是 O ( n ) O(n) O(n)的。

对于第二种情况,我们令 f x , y f_{x,y} fx,y表示一个端点在 x x x子树内,一个在 y y y子树内边的数量,答案就是 d x − d y + 2 ⋅ f x , y d_x-d_y+2\cdot f_{x,y} dxdy+2fx,y

考虑在搜索整颗树的时候用数据结构来维护对于当前点 x x x的每个点的 d y + 2 ⋅ f x , y d_y+2\cdot f_{x,y} dy+2fx,y
首先我们需要把 x x x子树的 f f f进行合并,对于一条端点恰好为 x x x的非树边 ( x , v ) (x,v) (x,v),需要把 v v v到根路径上的 − d + 2 f -d+2f d+2f都减去 2 2 2
这里我们显然可以用树链剖分+线段树来实现。

但若是直接做的话,时间复杂度和空间复杂度都是 O ( m log ⁡ 2 n ) O(m\log ^2 n) O(mlog2n)的,空间上只有 64 MB 64\text{MB} 64MB并不能接受。

不妨采用 DSU ON TREE \text{DSU ON TREE} DSU ON TREE的思想, DFS \text{DFS} DFS的时候先进入重儿子,始终保留重儿子的线段树。对于轻儿子线段树在依次合并时回收空闲节点。那这样 DFS \text{DFS} DFS到一个点的时候,需要的线段树个数就是这个点到根路径上轻边条数。

实现上的话,可以采用标记永久化,当然标记下放也是一样的。那么我们可以先按 DFS \text{DFS} DFS序建出一棵原始的线段树,这样只需要在每个节点上打个 t a g tag tag就可以了。

于是这样做空间复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn)的了。

【参考代码】

#include
using namespace std;

const int N=2e4+10,M=2e5+10,K=M*6;
int n,m,ind,ans,a[N],d[N],rt[N];
int top[N],siz[N],fa[N],son[N],pos[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void gmin(int &x,int y){x=min(x,y);}

namespace Segment
{
#define lc (x<<1)
#define rc (x<<1|1)
	queue<int>q;
	struct Seg
	{
		int w[K],ls[K],rs[K],tar[K],val[K];
		void build(int x,int l,int r)
		{
			if(l==r){w[x]=a[l];return;}
			int mid=(l+r)>>1;
			build(lc,l,mid);build(rc,mid+1,r);
			w[x]=min(w[lc],w[rc]);
		}
		int newnode(int x){int y=q.front();q.pop();ls[y]=rs[y]=tar[y]=0;val[y]=w[x];return y;}
		void pushup(int y,int x)
		{
			if(!ls[y]) ls[y]=newnode(lc);
			if(!rs[y]) rs[y]=newnode(rc);
			val[y]=min(val[ls[y]],val[rs[y]]);
		}
		void pushdown(int y,int x)
		{
			if(tar[y])
			{
				if(!ls[y]) ls[y]=newnode(lc);
				if(!rs[y]) rs[y]=newnode(rc);
				tar[ls[y]]+=tar[y];val[ls[y]]+=tar[y];tar[rs[y]]+=tar[y];val[rs[y]]+=tar[y];
				tar[y]=0;
			}
		}
		void update(int &y,int x,int l,int r,int L,int R,int v)
		{
			if(!y) y=newnode(x);
			if(L<=l && r<=R) {tar[y]+=v;val[y]+=v;return;}
			pushdown(y,x);
			int mid=(l+r)>>1;
			if(L<=mid) update(ls[y],lc,l,mid,L,R,v);
			if(R>mid) update(rs[y],rc,mid+1,r,L,R,v);
			pushup(y,x);
		}
		void upchain(int &x,int y)
		{
			while(top[y]^1) update(x,1,1,n,pos[top[y]],pos[y],-2),y=fa[top[y]];
			if(y>1) update(x,1,1,n,2,pos[y],-2);
		}
		int merge(int y,int z,int x,int l,int r)
		{
			if(!y || !z) return y+z;
			if(l==r) tar[y]+=tar[z];
			else
			{
				pushdown(y,x);pushdown(z,x);
				int mid=(l+r)>>1;
				ls[y]=merge(ls[y],ls[z],lc,l,mid);
				rs[y]=merge(rs[y],rs[z],rc,mid+1,r);
				pushup(y,x);
			}
			q.push(z);return y;
		}
		void clear(int x,int l,int r)
		{
			if(!x) return; q.push(x);
			if(l==r) return;
			int mid=(l+r)>>1;
			clear(ls[x],l,mid);clear(rs[x],mid+1,r);//wrong because write lc,rc
		}
	}tr;
#undef lc
#undef rc
}
using namespace Segment;

namespace Graph
{
	struct Tway{int v,nex;};
	struct Gra
	{
		Tway e[M];int tot,head[N];
		void add(int u,int v)
		{
			e[++tot]=(Tway){v,head[u]};head[u]=tot;
			e[++tot]=(Tway){u,head[v]};head[v]=tot;
		}
		void clear(){tot=0;for(int i=0;i<=n;++i)head[i]=0;}
	}T,G;

	void dfs1(int x)
	{
		siz[x]=1;
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(v==fa[x]) continue;
			fa[v]=x;dfs1(v);siz[x]+=siz[v];d[x]+=d[v];
			if(siz[v]>siz[son[x]]) son[x]=v;//wrong because write siz[x]>..
		}
		if(x>1)
		{
			for(int i=T.head[x];i;i=T.e[i].nex)
				if(T.e[i].v^fa[x]) gmin(ans,d[x]-d[T.e[i].v]);
		}
	}
	void dfs2(int x,int tp)
	{
		pos[x]=++ind;top[x]=tp;
		if(son[x]) dfs2(son[x],tp);
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(v==fa[x] || v==son[x]) continue;
			dfs2(v,v);
		}
	}
	void dfs3(int x)
	{
		if(son[x]) dfs3(son[x]),rt[x]=rt[son[x]];
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(v==fa[x] || v==son[x]) continue;
			dfs3(v);rt[x]=tr.merge(rt[x],rt[v],1,1,n);//wrong because forgot to write dfs3
		}
		if(x==1) return;
		for(int i=G.head[x];i;i=G.e[i].nex) tr.upchain(rt[x],G.e[i].v);
		tr.update(rt[x],1,1,n,pos[x],pos[x],M);gmin(ans,d[x]+tr.val[rt[x]]);tr.update(rt[x],1,1,n,pos[x],pos[x],-M);
	}
}
using namespace Graph;

namespace DreamLolita
{
	void clear()
	{
		T.clear();G.clear();tr.clear(rt[1],1,n);
		for(int i=0;i<=n;++i) fa[i]=siz[i]=son[i]=rt[i]=d[i]=top[i]=0;
		ind=0;
	}
	void init()
	{
		n=read();m=read();ans=M;
		for(int i=1;i<n;++i) T.add(read(),read());
		for(int i=n,x,y;i<=m;++i) d[x=read()]++,d[y=read()]++,G.add(x,y);
	}
	void solution(int cas)
	{
		init();dfs1(1);dfs2(1,1);
		for(int i=1;i<=n;++i) a[pos[i]]=d[i];
		tr.build(1,1,n);dfs3(1);
		printf("Case #%d: %d\n",cas,ans+2);
		clear();
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("HDU5511.in","r",stdin);
	freopen("HDU5511.out","w",stdout);
#endif
	int T=read();for(int i=1;i<K;++i) q.push(i);
	for(int i=1;i<=T;++i) DreamLolita::solution(i);
	return 0;
}

你可能感兴趣的:(数据结构-线段树)