CF487E-Tourists

题意

给出一个(n)个点(m)条边的无向图,给出每个点的初始点权,(q)次操作:

  • 修改一个点的点权
  • 询问两点间的所有路径中最小点权最小的路径上的最小点权

(n,m,qle 10^5,wle 10^9)

分析

一个结论

一个经典的结论是:一个点数大于等于3的点双连通分量中对于任意不同的三点(a,b,c),必定存在一条简单路径(a)走到(b)经过(c)

证明

(来源:codeforces题解中的证明)

首先点双连通分量的定义是,任意两个不同点之间必定存在至少两条点不重复的简单路径(首位不算)。也就是说,在点双连通分量中,任意删去一个点,这个连通分量仍然是一个连通块。

边双连通分量的定义是任意两个不同点之间必定存在至少两条边不重复简单路径,即删去任意一条边连通分量仍然是一个连通块。

很明显,点双连通分量是包含边双连通分量的,所以在点双连通分量中删去一条边也不会导致分量不连通(考虑删去这条边的任意一个端点)。

清楚定义后,我们就可以开始证明上面的结论了。

构造网络。对原图中的点进行拆点,(u)变成两个点(u_1,u_2),连边((u_1,u_2,1)),表示一个点的容量为1,即只能经过一次。对于原图中原有的边((u,v)),连边((u_2,v_1,1)),表示这条边也只能经过一次。对于任意选定的三点(a,b,c),连边:((S,c_1,2),(a_2,T,1),(b_2,T,1)),那么若这个网络的最大流为2,就说明这样一条简单路径存在。

由最大流最小割定理,要证明这个网络的最大流一定为2,只要证明网络的最小割一定为2 。考虑这个网络的最小割(C),从连边可以得到(Cle 2),接下来证明(C>1)

割掉((S,c_1,2))会让(C=2),割掉((a_2,T,1))((b_2,T,1))都不足以让网络不连通。剩下的有两种边,一种是拆点拆出来的边,一种是原图中有的边。对于边((u_1,u_2,1)),我们割掉这条边相当于是删掉这个点,而由点双连通分量的定义可知,删掉任意一个点原图仍然连通,故(C>1)。另一种边是((u_2,v_1,1)),由点双连通分量包含边双连通分量的性质可知,删去任意一条边原图仍然连通,故(C>1) 。综上,我们有(Cle 2,C>1),又由连边可知(Cin mathbb Z^+),所以(C=2)

于是证明了结论。

做法

有了上面的结论,我们可以知道,如果一条路径经过了一个点双连通分量,那么它一定可以取得这个点双连通分量中权值最小的点(绕过去)。由于一个点可以被很多点双连通分量包含(割点),但只能被一个父点双连通分量包含,所以我们考虑把这个图建成一棵树,每一个割点对删去它之后的每一个连通块建一个特殊节点,这个特殊节点连向这个连通块内的所有节点。这样每个特殊节点只有一个父亲(割点),所以它们构成了一棵树,点数为(O(n))

一条路径的查询,如果它经过一个点双连通分量,那么必定可以取得其中的最小值,所以对每一个特殊点开一个multiset,记录这个点双连通分量中的最小点权,每次修改的时候修改当前节点的(w)值和父特殊点(正常点若有父亲一定是特殊点,特殊点的父亲一定是正常点)的multiset,用树链剖分和线段树维护一下路径最小值即可(也可以用LCT)。

如果查询的两点的lca为特殊点,那么其实它的父亲(割点)的信息是不在里面的,但也是可以走到的,所以特殊处理一下即可。

这题我写的时候在求点双连通分量的时候遇到了一点麻烦。边双连通分量可以先把桥求出来再跑一次dfs不走桥,点双连通分量则要把边压进栈,对于每一个未走过的点的出边,判断是否( ext{low}[v]ge ext{dfn}[v]),若是的话就弹栈并把边中的点记录一下(或者操作一下什么的),知道遇到当前边。这是因为一个割点可能是很多个点双连通分量的割顶,所以直接用点来处理会出问题(少一些点),而边是不会遗漏的。每一条出边都是一个新的点双连通分量。

时间复杂度(O(nlog ^2n))

代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
int read() {
	int x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const int inf=1e9+7;
const int maxn=2e5+1;
int w[maxn],n,m,q,all,back[maxn];
multiset<int> st[maxn];
inline void Min(int &x,int y) {x=min(x,y);}
namespace sgt {
	int t[maxn<<2];
	void build(int x,int l,int r) {
		if (l==r) {
			int p=back[l];
			t[x]=(p>n?*st[p].begin():w[p]);
			return;
		}
		int mid=(l+r)>>1;
		build(x<<1,l,mid),build(x<<1|1,mid+1,r);
		t[x]=min(t[x<<1],t[x<<1|1]);
	}
	void modify(int x,int l,int r,int p,int d) {
		if (l==r) {
			t[x]=d;
			return;
		}
		int mid=(l+r)>>1;
		p<=mid?modify(x<<1,l,mid,p,d):modify(x<<1|1,mid+1,r,p,d);
		t[x]=min(t[x<<1],t[x<<1|1]);
	}
	void modify(int p,int d) {
		modify(1,1,all,p,d);
	}
	int query(int x,int L,int R,int l,int r) {
		if (L==l && R==r) return t[x];
		int mid=(L+R)>>1;
		if (r<=mid) return query(x<<1,L,mid,l,r); 
		if (l>mid) return query(x<<1|1,mid+1,R,l,r); 
		return min(query(x<<1,L,mid,l,mid),query(x<<1|1,mid+1,R,mid+1,r));
	}
	int query(int l,int r) {
		if (l>r) return inf;
		return query(1,1,all,l,r);
	}
}
namespace tree {
	int dfx=0,first[maxn],second[maxn],top[maxn],size[maxn],son[maxn],dep[maxn],fat[maxn];
	vector<int> g[maxn];
	void add(int x,int y) {g[x].push_back(y);}
	int dfs(int x,int fa) {
		fat[x]=fa;
		dep[x]=dep[fa]+1;
		int &sz=size[x]=1,&sn=son[x]=0;
		for (int v:g[x]) {
			sz+=dfs(v,x);
			if (size[v]>size[sn]) sn=v;
		}
		return sz;
	}
	void Top(int x,int tp) {
		top[x]=tp;
		back[first[x]=++dfx]=x;
		if (son[x]) Top(son[x],tp);
		for (int v:g[x]) if (v!=son[x]) Top(v,v);
		second[x]=dfx;
	}
	int lca(int x,int y) {
		for (;top[x]!=top[y];dep[top[x]]>dep[top[y]]?x=fat[top[x]]:y=fat[top[y]]);
		return dep[x]<dep[y]?x:y;
	}
	void change(int x,int y) {
		if (x!=1) {
			int f=fat[x];
			multiset<int> &mt=st[f];
			mt.erase(mt.find(w[x]));
			mt.insert(y);
			sgt::modify(first[f],*mt.begin());
		}
		w[x]=y;
		sgt::modify(first[x],y);
	}
	int query(int x,int y) {
		if (x==y) return w[x];
		int l=lca(x,y),ret=inf;
		for (int i=top[x];dep[i]>=dep[l];i=top[x=fat[i]]) Min(ret,sgt::query(first[i],first[x]));
		if (dep[x]>=dep[l]) Min(ret,sgt::query(first[l],first[x]));
		for (int i=top[y];dep[i]>dep[l];i=top[y=fat[i]]) Min(ret,sgt::query(first[i],first[y]));
		if (dep[y]>dep[l]) Min(ret,sgt::query(first[l]+1,first[y]));
		if (l>n) Min(ret,w[fat[l]]);
		return ret;
	}
}
namespace graph {
	int dfx=0,low[maxn],dfn[maxn],top=0,tic[maxn];
	pair<int,int> sta[maxn];
	bool ins[maxn];
	vector<int> g[maxn];
	void add(int x,int y) {g[x].push_back(y);}
	void Tarjan(int x) {
		ins[x]=true;
		dfn[x]=low[x]=++dfx;
		for (int v:g[x]) if (!dfn[v]) {
			pair<int,int> b=sta[++top]=make_pair(x,v);
			Tarjan(v);
			if (low[v]>=dfn[x]) {
				int spe=++all;
				tree::add(x,spe);
				while (true) {
					pair<int,int> p=sta[top--];
					int fir=p.first,sec=p.second;
					if (fir!=x && tic[fir]!=dfn[x]) tic[fir]=dfn[x],tree::add(spe,fir),st[spe].insert(w[fir]);
					if (sec!=x && tic[sec]!=dfn[x]) tic[sec]=dfn[x],tree::add(spe,sec),st[spe].insert(w[sec]);
					if (p==b) break;
				}
			}
			low[x]=min(low[x],low[v]);
		} else if (ins[v]) low[x]=min(low[x],dfn[v]);
		ins[x]=false;
	}
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	n=all=read(),m=read(),q=read();
	for (int i=1;i<=n;++i) w[i]=read();
	for (int i=1;i<=m;++i) {
		int x=read(),y=read();
		graph::add(x,y),graph::add(y,x);
	}
	graph::Tarjan(1);
	tree::dfs(1,0);
	tree::Top(1,1);
	sgt::build(1,1,all);
	while (q--) {
		static char o[3];
		scanf("%s",o);
		int x=read(),y=read();
		if (o[0]=='C') tree::change(x,y); else 
		if (o[0]=='A') {
			int ans=tree::query(x,y);
			printf("%d
",ans);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/owenyu/p/7190120.html