CF487E Tourists

背景:

越来越困了。

题目传送门:

https://www.luogu.org/problem/CF487E

题意:

一个图,支持修改点权,支持查询 x , y x,y x,y之间所有不重边路径的最小值。

思路:

若这个问题在树上就是一道大水题了,直接树剖维护即可。
但是这个问题在图上,将图变成树的一种操作是圆方树,不妨考虑圆方树来解决。
由于我们知道若有路径经过一个点双,则这个点双对答案的贡献应该为这个点双的最小值,因此我们可以存在方点上,可以用 multiset \text{multiset} multiset或线段树来实现。
但是若为一个菊花图这样的修改是的时间复杂度是 Θ ( n ) \Theta(n) Θ(n),总的时间复杂度就为 Θ ( n m ) \Theta(nm) Θ(nm)的,显然会被卡掉。

考虑如何快速修改。
当前的方点不维护它父亲的贡献
将一个方点的贡献记录在它的父亲,某一个圆点上面,那么我们每一次修改就只用统计路径上的最小值;若这两个点在圆方树上的 lca \text{lca} lca为方点,再对它的父亲求一个最小值即可(因为它的父亲也一定属于这个点双)。
修改的时候在线段树中修改当前的位置,然后在它的父亲(圆点的父亲一定是方点)的 multiset \text{multiset} multiset删掉原来的权值,加入新的,然后对应的线段树修改即可。
这个仔细思考一下就可以了。
代码实现也不难。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define ID multiset<int>::iterator
using namespace std;
multiset<int> F[400010];
	int n,m,q,op,len=0,lena=0,lenb=0;
	int val[400010],lasta[400010],lastb[400010],sta[400010],dfn[400010],low[400010];
	int top[400010],dep[400010],size[400010],fa[400010],son[400010],id[400010];
	struct node1{int x,y,next;} a[800010],b[800010];
	struct node2{int l,r,lc,rc,mi;} tr[800010];
void insa(int x,int y)
{
	a[++lena]=(node1){x,y,lasta[x]}; lasta[x]=lena;
}
void insb(int x,int y)
{
	b[++lenb]=(node1){x,y,lastb[x]}; lastb[x]=lenb;
}
int TOP=0,cnt=0;
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	sta[++TOP]=x;
	for(int i=lastb[x];i;i=b[i].next)
	{
		int y=b[i].y;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x])
			{
				op++,insa(op,x),insa(x,op);
				int tmp;
				do
				{
					tmp=sta[TOP--];
					insa(op,tmp),insa(tmp,op);
				} while(tmp!=y);
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
}
void dfs1(int x)
{
	size[x]=1;
	for(int i=lasta[x];i;i=a[i].next)
	{
		int y=a[i].y;
		if(y==fa[x]) continue;
		dep[y]=dep[x]+1;
		fa[y]=x;
		if(x>n) F[x].insert(val[y]);
		dfs1(y);
		size[x]+=size[y];
		if(size[y]>size[son[x]]) son[x]=y;
	}
}
int cntt=0;
void dfs2(int x,int tp)
{
	id[x]=++cntt;
	top[x]=tp;
	if(son[x]) dfs2(son[x],tp);
	for(int i=lasta[x];i;i=a[i].next)
	{
		int y=a[i].y;
		if(y==fa[x]||y==son[x]) continue;
		dfs2(y,y);
	}
}
void build(int l,int r)
{
	int now=++len;
	tr[now]=(node2){l,r,-1,-1,2147483647};
	if(l<r)
	{
		int mid=(l+r)>>1;
		tr[now].lc=len+1; build(l,mid);
		tr[now].rc=len+1; build(mid+1,r);
	}
}
void change(int now,int x,int d)
{
	if(tr[now].l==tr[now].r)
	{
		tr[now].mi=d;
		return;
	}
	int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)>>1;
	if(x<=mid) change(lc,x,d); else change(rc,x,d);
	tr[now].mi=min(tr[lc].mi,tr[rc].mi);
}
int findmin(int now,int l,int r)
{
	if(tr[now].l==l&&tr[now].r==r) return tr[now].mi;
	int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)>>1;
	if(r<=mid) return findmin(lc,l,r);
	else if(l>mid) return findmin(rc,l,r);
	else return min(findmin(lc,l,mid),findmin(rc,mid+1,r));
}
int solve(int x,int y)
{
	int tx=top[x],ty=top[y];
	int mi=2147483647;
	while(tx!=ty)
	{
		if(dep[tx]>dep[ty]) swap(x,y),swap(tx,ty);
		mi=min(mi,findmin(1,id[ty],id[y]));
		y=fa[ty];ty=top[y];
	}
	if(dep[x]>dep[y]) swap(x,y);
	mi=min(mi,findmin(1,id[x],id[y]));
	if(x>n) mi=min(mi,val[fa[x]]);
	return mi;
}
int main()
{
	char s[10];
	int x,y;
	scanf("%d %d %d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		scanf("%d",&val[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		insb(x,y),insb(y,x);
	}
	op=n;
	tarjan(1);
	dfs1(1);
	dfs2(1,1);
	build(1,op);
	for(int i=1;i<=n;i++)
		change(1,id[i],val[i]);
	for(int i=n+1;i<=op;i++)
		change(1,id[i],*F[i].begin());
	for(int i=1;i<=q;i++)
	{
		scanf("%s %d %d",s+1,&x,&y);
		if(s[1]=='C')
		{
			if(fa[x]) F[fa[x]].erase(F[fa[x]].lower_bound(val[x])),F[fa[x]].insert(y),change(1,id[fa[x]],*F[fa[x]].begin());
			val[x]=y,change(1,id[x],val[x]);
		}
		else
		{
			printf("%d\n",solve(x,y));
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值