CF487E Tourists

题目链接

题解

先考虑比较特殊的情况:如果A和B在一个点双内,那么答案就是这个点双的最小值。否则,考虑对点双缩点构建圆方树,于是A到B中经过的每一个方点都代表由这个点双的一个点进入,另一个点出来,这样一定可以取到这个点双的最小值,于是把方点的权值设为这个点双的最小值。这样,答案就是路径的最小值了,树剖维护即可。

剩下的问题在于动态维护方点的权值,如果直接做在修改时把所有连边改一遍效率会炸,考虑利用树的形态:每个方点只取它的儿子的最小值,这样修改一个点只会修改到它的父亲,查询的时候,不在顶点上的方点会被父亲算到,如果顶点是方点就再算一次它的父亲,用multiset维护每个方点的儿子的值,效率 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,F=1e9+5;
int n,m; vector<int>h[N];
int q[N],t,dn[N],lw[N],ct;
int pt,w[N],hd[N],to[N],nx[N],tt;
multiset<int>st[N];
void add(int u,int v){
	//cout<<u<<" "<<v<<endl;
	nx[++tt]=hd[u]; to[hd[u]=tt]=v;
}
void dfs(int u,int fa){ //cout<<u<<" "<<fa<<endl;
	dn[u]=lw[u]=++ct; q[++t]=u;
	int z=h[u].size();
	for(int i=0;i<z;i++){
		int v=h[u][i];
		if(!dn[v]){
			dfs(v,u);
			if(lw[v]>=dn[u]){
				pt++;
				while(q[t]!=v) add(q[t],pt),add(pt,q[t]),t--;
				add(q[t],pt),add(pt,q[t]),t--;
				add(u,pt),add(pt,u);
			}
			lw[u]=min(lw[u],lw[v]);
		}
		else if(v!=fa) lw[u]=min(lw[u],dn[v]);
	}
}
int fa[N],dp[N],sz[N],sn[N],tp[N],id[N];
void cnt(int u){
	multiset<int>:: iterator it=st[u].begin();
	w[u]=(*it);
}
void d1(int u){
	sz[u]=1;
	for(int e=hd[u];e;e=nx[e]) if(to[e]!=fa[u]){
		int v=to[e]; fa[v]=u; dp[v]=dp[u]+1;
		d1(v); sz[u]+=sz[v];
		if(sz[v]>sz[sn[u]]) sn[u]=v;
		if(u>pt) st[u].insert(w[v]);
	}
	if(u>pt) cnt(u);
}
void d2(int u){
	dn[u]=++ct; id[ct]=u;
	if(sn[u]) tp[sn[u]]=tp[u],d2(sn[u]);
	for(int e=hd[u];e;e=nx[e]){
		int v=to[e];
		if(v==fa[u] || v==sn[u]) continue;
		tp[v]=v; d2(v);
	}
}
#define mid ((l+r)>>1)
#define lc (u<<1)
#define rc ((u<<1)|1)
int mn[N<<2];
void bld(int u,int l,int r){
	if(l==r){
		mn[u]=w[id[l]]; return;
	}
	bld(lc,l,mid); bld(rc,mid+1,r);
	mn[u]=min(mn[lc],mn[rc]);
}
void upd(int u,int l,int r,int x){
	if(l==r){
		mn[u]=w[id[l]]; return;
	}
	if(x<=mid) upd(lc,l,mid,x);
	else upd(rc,mid+1,r,x);
	mn[u]=min(mn[lc],mn[rc]);
}
int qry(int u,int l,int r,int a,int b){
	//if(u==1) printf("%d %d\n",a,b);
	if(a<=l && r<=b) return mn[u];
	int mi=F;
	if(a<=mid) mi=qry(lc,l,mid,a,b);
	if(b>mid) mi=min(mi,qry(rc,mid+1,r,a,b));
	return mi;
}
int main()
{
	srand(time(0));
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	int T; cin>>n>>m>>T;
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int u,v,i=1;i<=m;i++)
		scanf("%d%d",&u,&v),h[u].push_back(v),h[v].push_back(u);
	pt=n; dfs(1,0); swap(n,pt); //return 0;
	ct=0; dp[1]=1; d1(1); ///return 0;
	tp[1]=1; d2(1); //return 0;
	bld(1,1,n);
	while(T--){
		int a,b; char c; scanf(" %c %d%d",&c,&a,&b);
		if(c=='C'){
			int f=fa[a];
			if(f){
				st[f].erase(w[a]); st[f].insert(b);
				cnt(f); upd(1,1,n,dn[f]);
			}
			w[a]=b; upd(1,1,n,dn[a]);
		}
		else{
			int mn=1e9;
			while(tp[a]!=tp[b]){
				if(dp[tp[a]]<dp[tp[b]]) swap(a,b);
				mn=min(mn,qry(1,1,n,dn[tp[a]],dn[a]));
				a=fa[tp[a]];
			}
			//cout<<"mn="<<mn<<endl;
			if(dp[a]>dp[b]) swap(a,b);
			if(a>pt && fa[a]) mn=min(mn,w[fa[a]]);
			mn=min(mn,qry(1,1,n,dn[a],dn[b]));
			printf("%d\n",mn);
		}
	}
	return 0;
}
/*
3 3 3
1
2
3
1 2
2 3
1 3
A 2 3
C 1 5
A 2 3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值