【CodeForces487E】【UOJ30】Tourists

【题目链接】

【思路要点】

  • 首先我们来证明点双连通分量的一个性质。
  • 引理:在一个点双连通分量中,给定任意三个不同的点\(a\),\(b\),\(c\),一定存在一条从\(a\)到\(c\)的,经过每个点至多一次的简单路径经过了\(b\)。
  • 证明:考虑网络流。在原图中存在无向边的点对之间建立无向边,容量为1;对每个点拆点限流,容量为1;由\(a\)和\(c\)向汇点连边,容量为1;由源点向\(b\)连边,容量为2。显然,原命题等价于源点到汇点的最大流等于2。显然源点到汇点的最大流不超过2,现在我们来证明它大于1。考虑最小割,我们发现割去一条点对之间的无向边等价于删去原图中的一条边,割去一条限流边等价于删去原图中的一个点,由于原图点双连通,我们无法只割去一条容量为1的边使得源点到汇点不连通,因此最小割大于1,原命题得证。
  • 那么,我们建立圆方树,定义圆点的权值为其本身的权值,方点的权值为其内部所有圆点权值的最小值,这一点可以使用一个堆来维护。
  • 询问就等价于询问树上两点之间权值的最小值,可以通过树链剖分+线段树实现。
  • 但我们发现修改一个圆点可能会导致\(O(N)\)个方点的权值发生变化,因此复杂度有可能会退化。
  • 考虑圆方树上两个圆点之间的路径,令它们的Lca为\(x\)。
  • 若\(x\)为一个圆点,那么路径上经过的每一个方点的父亲圆点也均被经过了。
  • 若\(x\)为一个方点,那么路径上经过的每一个不是\(x\)的方点的父亲圆点也均被经过了。
  • 因此,我们将每一个圆点的权值附加在其唯一的父亲方点上,在询问时特判\(x\)为一个方点的情况即可。
  • 时间复杂度\(O(N+M+QLog^2N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
const int MAXP = 400005;
const int INF = 1e9;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct SegmentTree {
	struct Node {
		int lc, rc;
		int Min;
	} a[MAXP];
	int root, n, size;
	void build(int &root, int l, int r) {
		root = ++size;
		a[root].Min = INF;
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
	}
	void init(int x) {
		n = x;
		root = size = 0;
		build(root, 1, n);
	}
	void update(int root) {
		a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
	}
	void modify(int root, int l, int r, int pos, int x) {
		if (l == r) {
			a[root].Min = x;
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) modify(a[root].lc, l, mid, pos, x);
		else modify(a[root].rc, mid + 1, r, pos, x);
		update(root);
	}
	void modify(int pos, int x) {
		modify(root, 1, n, pos, x);
	}
	int query(int root, int l, int r, int ql, int qr) {
		if (l == ql && r == qr) return a[root].Min;
		int ans = INF, mid = (l + r) / 2;
		if (mid >= ql) chkmin(ans, query(a[root].lc, l, mid, ql, min(qr, mid)));
		if (mid + 1 <= qr) chkmin(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr));
		return ans;
	}
	int query(int l, int r) {
		return query(root, 1, n, l, r);
	}
} ST;
struct Heap {
	priority_queue <int, vector<int>, greater<int> > Heap, Delt;
	void push(int x) {Heap.push(x); }
	void delt(int x) {Delt.push(x); }
	int query() {
		while (!Delt.empty() && Heap.top() == Delt.top()) {
			Heap.pop();
			Delt.pop();
		}
		return Heap.top();
	}
} Heap[MAXN];
int n, m, q, oldn, val[MAXN];
int top, Stack[MAXN];
int timer, dfn[MAXN], low[MAXN], up[MAXN];
int depth[MAXN], father[MAXN], size[MAXN], son[MAXN];
vector <int> a[MAXN], b[MAXN];
void dfs(int pos, int fa) {
	size[pos] = 1;
	father[pos] = fa;
	depth[pos] = depth[fa] + 1;
	for (unsigned i = 0; i < b[pos].size(); i++)
		if (b[pos][i] != fa) {
			dfs(b[pos][i], pos);
			size[pos] += size[b[pos][i]];
			if (size[b[pos][i]] > size[son[pos]]) son[pos] = b[pos][i];
		}
}
void efs(int pos, int fa) {
	dfn[pos] = ++timer; up[pos] = fa;
	if (pos <= oldn) ST.modify(timer, val[pos]);
	else ST.modify(timer, Heap[pos].query());
	if (son[pos]) efs(son[pos], fa);
	for (unsigned i = 0; i < b[pos].size(); i++)
		if (b[pos][i] != father[pos] && b[pos][i] != son[pos]) efs(b[pos][i], b[pos][i]);
}
void tarjan(int pos) {
	Stack[++top] = pos;
	dfn[pos] = low[pos] = ++timer;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (dfn[a[pos][i]] == 0) {
			tarjan(a[pos][i]);
			chkmin(low[pos], low[a[pos][i]]);
			if (low[a[pos][i]] >= dfn[pos]) {
				int tmp = 0; n++;
				while (tmp != a[pos][i]) {
					tmp = Stack[top--];
					Heap[n].push(val[tmp]);
					b[n].push_back(tmp);
					b[tmp].push_back(n);
				}
				b[n].push_back(pos);
				b[pos].push_back(n);
			}
		} else chkmin(low[pos], dfn[a[pos][i]]);
}
int query(int x, int y) {
	int ans = INF;
	while (up[x] != up[y]) {
		if (depth[up[x]] < depth[up[y]]) swap(x, y);
		chkmin(ans, ST.query(dfn[up[x]], dfn[x]));
		x = father[up[x]];
	}
	if (dfn[x] > dfn[y]) swap(x, y);
	chkmin(ans, ST.query(dfn[x], dfn[y]));
	if (x > oldn) chkmin(ans, val[father[x]]);
	return ans;
}
int main() {
	read(n), read(m), oldn = n, read(q);
	for (int i = 1; i <= n; i++)
		read(val[i]);
	for (int i = 1; i <= m; i++) {
		int x, y;
		read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	tarjan(1);
	timer = 0;
	memset(dfn, 0, sizeof(dfn));
	dfs(1, 0);
	ST.init(n);
	efs(1, 1);
	for (int i = 1; i <= q; i++) {
		char opt; int x, y;
		scanf("\n%c%d%d", &opt, &x, &y);
		if (opt == 'C') {
			int old = val[x];
			val[x] = y;
			ST.modify(dfn[x], y);
			if (father[x]) {
				int f = father[x];
				Heap[f].delt(old);
				Heap[f].push(y);
				ST.modify(dfn[f], Heap[f].query());
			}
		} else writeln(query(x, y));
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值