【CF487E】 Tourists 简易题解

题目大意

         \ \ \ \ \ \ \,        给你 n n n 个点和 m m m条边的无向图,没有自环,没有重边,每个点上面有点权。

         \ \ \ \ \ \ \,        每次可能有两种操作:修改一个点的点权,或者询问两个点之间的路径上最小可能的点权是多少。

想法

         \ \ \ \ \ \ \,       有一个很显然的贪心想法,询问的时候肯定优先走较小权值路径,也就是在有分叉(点双)的时候走较小权值的那一侧,而只有可能最小的会造成贡献。

         \ \ \ \ \ \ \,       所以说我们可以尝试将每个点双之间建一个堆,记录最小的点权。

         \ \ \ \ \ \ \,       也就是建出一棵圆方树,方点上面一个堆,记录与他向连的圆点的权值最小值。

         \ \ \ \ \ \ \,       每次询问就只需要考虑经过唯一路径上面 圆点点权方点堆顶 的最小值就行了,这个可以用树链剖分搞。

         \ \ \ \ \ \ \,       然后考虑修改,便是修改与这个 圆点 相连的方点上面的堆就好了,删除原来的点权,加入新点权。但是这样子我们不能保证其复杂度,要是圆方树建成一个菊花图就会 T T T 飞了。

         \ \ \ \ \ \ \,       我们再考虑一下,我们树链剖分往上跳的时候,其实已经计算过 走过的方点 的父亲了(圆点),会有计算重复的地方。所以说 方点的堆 里面不需要记录其父亲的权值,换句话说,对于一个圆点,他的值只需要被他的 方点父亲 的堆记录,这样子每次修改,只需要修改他父亲的,加上线段树和堆的复杂度也不过一次 O ( log ⁡ n ) O(\log n) O(logn)

         \ \ \ \ \ \ \,       但是每次询问的是两个圆点,其 L C A LCA LCA有可能是方点,这个时候少计算了一个 方点 L C A LCA LCA 的父亲,需要注意加入判断一下。(第一个样第一次例询问输出为2有可能就是这样死的)

         \ \ \ \ \ \ \,       然后就是 圆方树+树链剖分+线段树+可删堆 套模板了,可删堆我是用 f h q _ T r e a p fhq\_Treap fhq_Treap 实现的,所以说代码比较丑。

         \ \ \ \ \ \ \,       预处理复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),修改操作复杂度 O ( log ⁡ n ) O(\log n) O(logn),询问操作复杂度 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<map>
#include<set>
using namespace std;
const int inf=0x3f3f3f3f;
const double eps=1e-10;
const double pi=acos(-1.0);
//char buf[1<<15],*S=buf,*T=buf;
//char getch(){return S==T&&(T=(S=buf)+fread(buf,1,1<<15,stdin),S==T)?0:*S++;}
inline int read(){
	int x=0,f=1;char ch;ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=0;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch&15);ch=getchar();}
	if(f)return x;else return -x;
}
const int N=4e5+200;
vector<int> G[N],E[N];
void add_E(int x,int y){E[x].push_back(y);E[y].push_back(x);}
void add_G(int x,int y){G[x].push_back(y);G[y].push_back(x);}
int sta[N],top,Size;
int tim,dfn[N],low[N];
void tarjan(int u){
  	dfn[u]=low[u]=++tim;
  	sta[++top]=u;
  	for(auto v:E[u]){
    	if(dfn[v]) low[u]=min(low[u],dfn[v]);
    	else{
      		tarjan(v),low[u]=min(low[u],low[v]);
      		if(low[v]>=dfn[u]){
        		++Size;int p;
        		while((p=sta[top])!=v)add_G(p,Size),top--;
        		p=sta[top],add_G(p,Size),top--;
        		add_G(u,Size);
      		}
    	}
  	}
}

struct node{int ls,rs,key,size,val;}T[N<<1];
#define lson T[rt].ls
#define rson T[rt].rs
int node_cnt;
queue<int>Rub;
int newnode(int x){
	int rt;
	if(!Rub.empty())rt=Rub.front(),Rub.pop();
	else rt=++node_cnt;
	lson=rson=0;
	T[rt].key=rand();T[rt].val=x;T[rt].size=1;
	return rt;
}
struct fhq_Treap{
	int root;
	void pushup(int rt){T[rt].size=T[lson].size+T[rson].size+1;}
	int merge(int a,int b){
		if(!a||!b)return a|b;
		if(T[a].key<T[b].key){T[a].rs=merge(T[a].rs,b);pushup(a);return a;}
		else{T[b].ls=merge(a,T[b].ls);pushup(b);return b;}
	}
	void split(int rt,int x,int &a,int &b){
		if(!rt){a=b=0;return;}
		if(T[rt].val<=x){a=rt;split(rson,x,rson,b);}
		else{b=rt;split(lson,x,a,lson);}
		pushup(rt);
	}
	void Insert(int x){
		int a,b;
		split(root,x,a,b);
		int rt=newnode(x);
		root=merge(merge(a,rt),b);
	}
	void Delete(int x){
		int a,b,c;
		split(root,x,a,c);
		split(a,x-1,a,b);
		b=merge(T[b].ls,T[b].rs);
		root=merge(merge(a,b),c);
	}
	int Min(){
		int rt=root;
		while(lson)rt=lson;
		return T[rt].val;
	}
}Treap[N];

int pos[N],w[N];
struct Segment_Tree{
  	#define Lson l,mid,rt<<1
  	#define Rson mid+1,r,rt<<1|1
  	int sum[N<<2];
  	void pushup(int rt){sum[rt]=min(sum[rt<<1],sum[rt<<1|1]);}
  	void build(int l,int r,int rt){
  		if(l==r){sum[rt]=w[pos[l]];return;}
  		int mid=(l+r)>>1;
  		build(Lson);build(Rson);
  		pushup(rt);
	}
  	void Update(int id,int c,int l,int r,int rt){
    	if(l==r){sum[rt]=c;return;}
    	int mid=(l+r)>>1;
    	if(id<=mid)Update(id,c,Lson);
    	else Update(id,c,Rson);
		pushup(rt);
  	}
  	int Query(int L,int R,int l,int r,int rt){
    	if(L<=l&&r<=R)return sum[rt];
    	int ret=1e9,mid=(l+r)>>1;
    	if(L<=mid)ret=min(ret,Query(L,R,Lson));
    	if(R>mid)ret=min(ret,Query(L,R,Rson));
    	return ret;
  	}
}Seg;

int n,m,Q;
struct Tree_Chain_Dissection{
	int idx[N];
	int deep[N],fa[N],son[N],size[N];
	int cnt,top[N];
	int dfs1(int u,int f,int dep){
		deep[u]=dep;fa[u]=f;size[u]=1;
		Treap[fa[u]].Insert(w[u]);
		int maxson=-1;
	  	for(auto v:G[u])if(v!=f){
		  	size[u]+=dfs1(v,u,dep+1);
		  	if(size[v]>maxson)maxson=size[v],son[u]=v;
		}
		return size[u];
	}
	void dfs2(int u,int topf){
		idx[u]=++cnt;top[u]=topf;
		pos[cnt]=u;
		if(!son[u])return;
		dfs2(son[u],topf);
	  	for(auto v:G[u])if(!idx[v])dfs2(v,v);
	}
	void init(){
		dfs1(1,0,1);
		dfs2(1,1);
	  	for(int i=n+1;i<=Size;++i)w[i]=Treap[i].Min();
		Seg.build(1,Size,1);
	}
	void Update(int u,int val){
	  	if(fa[u]){
	    	Treap[fa[u]].Delete(w[u]);
	    	Treap[fa[u]].Insert(val);
	    	Seg.Update(idx[fa[u]],Treap[fa[u]].Min(),1,Size,1);
	  	}
	  	w[u]=val;
		Seg.Update(idx[u],val,1,Size,1);
	}
	int Query(int x,int y){
	  	int ans=inf;
	  	while(top[x]!=top[y]){
	    	if(deep[top[x]]<deep[top[y]])swap(x,y);
	    	ans=min(ans,Seg.Query(idx[top[x]],idx[x],1,Size,1));
	    	x=fa[top[x]];
	  	}
	 	if(deep[x]>deep[y])swap(x,y);
	  	ans=min(ans,Seg.Query(idx[x],idx[y],1,Size,1));
    	if(x<=n)return ans;
    	else return min(ans,w[fa[x]]);
	}
}TCD;

char op[2];
int a,b;

int main()
{
	srand(time(NULL));
	Size=n=read();m=read();Q=read();w[0]=inf;
  	for(int i=1;i<=n;++i)w[i]=read();
  	for(int i=1,a,b;i<=m;++i)
	a=read(),b=read(),add_E(a,b);
  	tarjan(1);
  	TCD.init();
  	while(Q--){
    	scanf("%s",op);a=read(),b=read();
    	if(op[0]=='C')TCD.Update(a,b);
    	if(op[0]=='A')printf("%d\n",TCD.Query(a,b));
  	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值