广义圆方树+树链剖分+set(Codeforces Round #278 (Div. 1): E. Tourists)

 

前置:双联通分量圆方树树链剖分

什是是广义圆方树

圆方树是针对于仙人掌建树,而广义圆方树是针对无向图建树,对于一个无向图

  • 无向图中的所有点 → 广义圆方树中的所有圆点 
  • 无向图中的一个双联通分量 → 广义圆方树中的其中一个方点,这个方点向当前双联通分量中的所有点连边
  • 无向图中的一条不在任何环中的边 → 广义圆方树中的其中一个方点,这个方点向当前边的两个端点连边

一张非常形象的图,来源于一个课件

其实看了这个图应该就什么都懂了,一些性质也很容易被发现hh

例如:广义图方树中一定是圆点方点相间,即圆点的父亲一定是方点,方点的父亲一定是圆点

 

一道例题:

https://codeforc.es/contest/487/problem/E

中文翻译:https://www.luogu.org/problemnew/show/CF487E

 

 

中文题意在上面那个链接里面

考虑起点u终点v,很容易发现答案就是u点到v点过程中经过的所有双联通分量中权值最小的那个点

建立广义圆方树,规定

  • 所有圆点的权值 → 原图中点的权值
  • 所有方点的权值 → 所有儿子的权值最小值(别忘了它所有儿子一定都是圆点)

对于每次询问

  • 如果u和v的LCA是圆点 → 答案就是u到v这条路径的最小值
  • 如果u和v的LCA是方点 → 答案就是u到v这条路径的最小值和方点父亲的值的最小值

修改就不用说了,然后这完全就是个树上修改+查询的模板问题,直接树链剖分+线段树就搞定了

 

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
#include<string>
#include<math.h>
#include<queue>
#include<set>
#include<stack>
#include<iostream>
using namespace std;
#define LL long long
#define mod 1000000007
vector<int> G[200005], T[205005];
multiset<int> Set[205005];
stack<int> st;
int n, t, cnt, val[205005], Time[205005], low[205005];
typedef struct RST
{
	int tre[814396];
	int t, fa[205005], size[205005], hson[205005], top[205005], dep[205005], rak[205005], id[205005];
	void Sech1(int u, int p)
	{
		int v, i;
		fa[u] = p, dep[u] = dep[p]+1;
		if(fa[u]>=n+1)
			Set[fa[u]].insert(val[u]);
		size[u] = 1;
		for(i=0;i<T[u].size();i++)
		{
			v = T[u][i];
			if(v==p)
				continue;
			Sech1(v, u);
			size[u] += size[v];
			if(size[v]>size[hson[u]])
				hson[u] = v;
		}
	}
	void Sech2(int u, int p)
	{
		int i, v;
		top[u] = p;
		rak[u] = ++t, id[t] = u;
		if(hson[u])
			Sech2(hson[u], p);
		for(i=0;i<T[u].size();i++)
		{
			v = T[u][i];
			if(v==fa[u] || v==hson[u])
				continue;
			Sech2(v, v);
		}
	}
	void Update(int l, int r, int x, int a, int b)
	{
		int m;
		if(l==r)
		{
			tre[x] = b;
			return;
		}
		m = (l+r)/2;
		if(a<=m)
			Update(l, m, x*2, a, b);
		else
			Update(m+1, r, x*2+1, a, b);
		tre[x] = min(tre[x*2], tre[x*2+1]);
	}
	int Query(int l, int r, int x, int a, int b)
	{
		int m, now;
		now = 1044266558;
		if(l>=a && r<=b)
			return tre[x];
		m = (l+r)/2;
		if(a<=m)
			now = min(now, Query(l, m, x*2, a, b));
		if(b>=m+1)
			now = min(now, Query(m+1, r, x*2+1, a, b));
		return now;
	}
	int TreQuery(int x, int y)
	{
		int now, p1, p2;
		p1 = top[x], p2 = top[y], now = 1044266558;
		while(p1!=p2)
		{
			if(dep[p1]<dep[p2])
				swap(p1, p2), swap(x, y);
			now = min(now, Query(1, cnt, 1, rak[p1], rak[x]));
			x = fa[p1], p1 = top[x];
		}
		if(dep[x]>dep[y])
			swap(x, y);
		now = min(now, Query(1, cnt, 1, rak[x], rak[y]));
		if(x>=n+1)
			now = min(now, val[fa[x]]);
		return now;
	}
}RT;
RT RST;
void Trajan(int u, int p)
{
	int i, v, now;
	st.push(u);
	Time[u] = low[u] = ++t;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		if(Time[v]==0)
		{
			Trajan(v, u);
			low[u] = min(low[u], low[v]);
			if(low[v]>=Time[u])
			{
				T[++cnt].push_back(u);
				T[u].push_back(cnt);
				while(st.empty()==0)
				{
					now = st.top(), st.pop();
					T[cnt].push_back(now);
					T[now].push_back(cnt);
					if(now==v)
						break;
				}
			}
		}
		else
			low[u] = min(low[u], Time[v]);
	}
}
void Update(int u, int c, int d)
{
	Set[u].erase(Set[u].find(c));
	Set[u].insert(d);
}
int main(void)
{
	char ch;
	int i, m, x, y, T;
	scanf("%d%d%d", &n, &m, &T);
	for(i=1;i<=n;i++)
		scanf("%d", &val[i]);
	cnt = n;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d", &x, &y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	Trajan(1, 0);
	RST.Sech1(1, 0);
	RST.Sech2(1, 1);
	for(i=1;i<=n;i++)
		RST.Update(1, cnt, 1, RST.rak[i], val[i]);
	for(i=n+1;i<=cnt;i++)
		RST.Update(1, cnt, 1, RST.rak[i], *Set[i].begin());
	while(T--)
	{
		scanf(" %c%d%d", &ch, &x, &y);
		if(ch=='C')
		{
			RST.Update(1, cnt, 1, RST.rak[x], y);
			if(RST.fa[x])
			{
				Update(RST.fa[x], val[x], y);
				RST.Update(1, cnt, 1, RST.rak[RST.fa[x]], *Set[RST.fa[x]].begin());
			}
			val[x] = y;
		}
		else
			printf("%d\n", RST.TreQuery(x, y));
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值