如果不是uoj上有的话(听说这是China Round),我有可能就错过这道题目了(这是我有史以来为oi写的最长的代码,用了我一天TAT!)。

题目

传送门

一个连通无向图,点上有权,支持两种操作:

  • 修改某点的权值
  • 询问点\(x\)到\(y\)的经过的最小的点(同一个点不能重复经过)

算法

这题想起来是不难的:

容易想到做一个强连通分量,每个分量里的点可以互相到达。

这时会有一个猜想:在一个分量里,是不是任意指定三点\(a,b,c\),都有一条合法路径从\(a\)到\(b\)途中经过\(c\)呢。我当时想了想,也没严谨证明,觉得是对的,现在做完了觉得有必要证明一下,然后我就在解答里找到了这个:

Consider a biconnected graph with at least 3 vertices. If we remove any vertex or any edge, the graph is still connected.

We build a network on the graph. Let's use (u,v,w) to describe a directed edge from u to v with capacity w. For each edge (u,v) of the original graph, we build (u,v,1) and (v,u,1). Build (S,c,2), (a,T,1) and (b,T,1). For each vertex other than S,T,c, we should give a capacity of 1 to the vertex.

In order to give capacity to vertex u, we build two vertices u1,u2 instead of u. For each (v,u,w), build (v,u1,w). For each (u,v,w), build(u2,v,w). Finally build (u1,u2,1).

Hence, if the maximal flow from S to T is 2, there is a simple path from a to b going through c.

Now we consider the minimal cut of the network. It is easy to find that minimal cut <= 2, so let's prove minimal cut > 1, which means, no matter which edge of capacity 1 we cut, there is still a path from S to T.

If we cut an edge like (u1,u2,1), it is equivalent to set the capacity of the vertex to 0, and equivalent to remove the vertex from the original graph. The graph is still connected, so there is still a path in the network.

If we cut other edges, it is equivalent to remove an edge from the original graph. It is still connected, too.

Now we have minimal cut > 1, which means maximal flow = minimal cut = 2. So there is always a simple path from a to b going through c.

这个证明不走寻常路,通过最小割来证明,服了!

这样子我们就可以将每个分量看作一个点生成一棵树,询问只需要做一个LCA,修改就用一个堆维护。因为有修改操作,所以就把LCA改为树链剖分。

问题在于,一点可能在许多个分量里,那么修改时需要修改很多堆,于是,可以把每个割点的值仅仅维护在它的父亲分量(这是相对于深度优先搜索树而言),不过这样询问时要记得特殊处理LCA的值,就是说它们的LCA有一个割点的信息在它们LCA的父亲上。构图类似下面的。

注:右边的每个点都是一个分量,并且里面的信息用堆维护。这里的连线是对应的点。点的颜色表示,左边某色的所有点的信息储存在右边相同颜色的点上。

也许你已经注意到了,有一个特殊地方,如果\(a\)和\(b\)相连,那么这里可以就把它们当作一个分量。并且为了写程序方便,故意在根结点的顶部多加了一个点。

代码

有两个很坑的地方(对于我来说):

  • 不要用set而是multiset
  • dfs子结点\(u\)后,不要漏了这一句(太久没写tarjan了):low[v] = min(low[v], low[u])

5.4k(已经过滤掉了0.8k的调试信息)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>
#include <set>
using namespace std; template <class t>
void tension(t &a, const t &b) {
if (b < a) {
a = b;
}
} template <class t>
void relax(t &a, const t &b) {
if (b > a) {
a = b;
}
} const int INF = 0x3f3f3f3f;
const int MAXN = (int) 1e5 + 3;
const int MAXM = (int) 1e5 + 3;
const int MAXSIZE = 1 << 19; int n, m, Q, A[MAXN]; struct Edge {
int to;
Edge *next;
}; struct BCC {
multiset<int> s;
void insert(int);
int maxval; BCC() {
maxval = INF;
} void change(int a, int b) {
s.erase(s.find(a));
s.insert(b);
maxval = *s.begin();
}
}; int cntBcc;
BCC bcc[MAXN * 2];
int idxNew[MAXN]; void BCC::insert(int x) {
s.insert(A[x]);
maxval = *s.begin();
} namespace zkwSegmentTree {
int depth;
int A[MAXSIZE + 1]; void init(int size) {
while ((1 << depth) - 2 < size) {
depth ++;
}
} void initModify(int x, int v) {
A[(1 << depth) + x] = v;
} void upgrade() {
for (int i = (1 << depth) - 1; i; i --)
A[i] = min(A[i << 1], A[i << 1 ^ 1]);
} int query(int a, int b) {
a = (1 << depth) + a - 1;
b = (1 << depth) + b + 1;
int ans = INF;
for (; a ^ b ^ 1; a >>= 1, b >>= 1) {
if (~a & 1) tension(ans, A[a ^ 1]);
if ( b & 1) tension(ans, A[b ^ 1]);
}
return ans;
} void modify(int a, int v)
{
a = (1 << depth) + a;
A[a] = v;
for (a >>= 1; a; a >>= 1)
A[a] = min(A[a << 1], A[a << 1 ^ 1]);
}
} namespace newGraph {
const int MAXNODE = MAXN * 2;
int root;
Edge *info[MAXNODE], mem[MAXNODE], *cur_mem = mem;
int fa[MAXNODE]; void insert(int f, int s) {
cur_mem->to = s;
cur_mem->next = info[f];
info[f] = cur_mem ++;
} int cntPath;
int top[MAXNODE], heavySon[MAXNODE];
int dep[MAXNODE], size[MAXNODE], belong[MAXNODE];
int idxMap[MAXNODE]; int cutVertex[MAXNODE]; void cutLightHeavy() {
static int que[MAXNODE];
int low = 0, high = 0;
que[high ++] = root;
fa[root] = -1;
while (low < high) {
int v = que[low ++];
for (Edge *pt = info[v]; pt; pt = pt->next) {
int u = pt->to;
que[high ++] = u;
dep[u] = dep[v] + 1;
fa[u] = v;
}
} for (int i = high - 1; i >= 0; i --) {
int v = que[i];
size[v] = 1;
heavySon[v] = -1;
for (Edge *pt = info[v]; pt; pt = pt->next) {
int u = pt->to;
size[v] += size[u];
if (heavySon[v] == -1 || size[u] > size[heavySon[v]]) {
heavySon[v] = u;
}
}
} zkwSegmentTree::init(cntBcc);
int cntIdx = 1;
for (int i = 0; i < high; i ++) {
int v = que[i];
if (idxMap[v] == 0) {
top[cntPath] = v;
for (; v != -1; v = heavySon[v]) {
idxMap[v] = cntIdx ++;
zkwSegmentTree::initModify(idxMap[v], bcc[v].maxval);
belong[v] = cntPath;
}
cntPath ++;
}
}
zkwSegmentTree::upgrade();
} int query(int a, int b) {
int ret = INF;
a = idxNew[a], b = idxNew[b]; while (belong[a] != belong[b]) {
if (dep[top[belong[a]]] < dep[top[belong[b]]]) {
swap(a, b);
}
tension(ret, zkwSegmentTree::query(idxMap[top[belong[a]]], idxMap[a]));
a = fa[top[belong[a]]];
}
if (dep[a] < dep[b]) swap(a, b);
tension(ret, zkwSegmentTree::query(idxMap[b], idxMap[a]));
tension(ret, A[cutVertex[b]]); return ret;
}
} namespace srcGraph {
Edge *info[MAXN], mem[MAXM * 2], *cur_mem = mem; void insert2(int a, int b) {
cur_mem->to = b;
cur_mem->next = info[a];
info[a] = cur_mem ++; cur_mem->to = a;
cur_mem->next = info[b];
info[b] = cur_mem ++;
} void init() {
for (int i = 0; i < m; i ++) {
int a, b;
scanf("%d%d\n", &a, &b);
insert2(a, b);
}
} int dfn[MAXN], low[MAXN], bccFa[MAXN]; void make() {
stack< pair<int, Edge*> > stk;
stk.push(make_pair(1, (Edge*) NULL) );
stack<int> contain; memset(dfn, -1, sizeof(dfn));
memset(low, -1, sizeof(low));
memset(bccFa, -1, sizeof(bccFa));
int cntDfn = 0; while (! stk.empty()) {
int v = stk.top().first;
Edge *&pt = stk.top().second;
if (! pt) {
dfn[v] = low[v] = cntDfn ++;
pt = info[v];
contain.push(v);
}
else {
int u = pt->to;
tension(low[v], low[u]); if (low[u] == dfn[v]) {
int newBcc = cntBcc ++;
while (true) {
int t = contain.top();
if (bccFa[t]) {
newGraph::insert(newBcc, bccFa[t]);
}
bcc[newBcc].insert(t);
if (bccFa[t] == -1) idxNew[t] = newBcc;
contain.pop();
if (t == u) break;
} if (bccFa[v] == -1) {
idxNew[v] = cntBcc;
bccFa[v] = cntBcc ++;
newGraph::cutVertex[bccFa[v]] = v;
}
newGraph::cutVertex[newBcc] = v;
newGraph::insert(bccFa[v], newBcc);
}
} for (; pt; pt = pt->next) {
int u = pt->to;
if (dfn[u] != -1) tension(low[v], dfn[u]);
else {
stk.push(make_pair(u, (Edge*) NULL));
break;
}
} if (! pt) stk.pop();
} newGraph::root = bccFa[1];
idxNew[1] = bccFa[1];
}
} int main() {
scanf("%d%d%d\n", &n, &m, &Q);
for (int i = 1; i <= n; i ++) {
scanf("%d\n", A + i);
}
srcGraph::init();
srcGraph::make();
newGraph::cutLightHeavy(); for (int i = 0; i < Q; i ++) {
char opt;
int a, b;
scanf("%c%d%d\n", &opt, &a, &b);
if (opt == 'A') {
int ans = a == b ? A[a] : newGraph::query(a, b);
printf("%d\n", ans);
}
else {
int t = idxNew[a];
if (t == newGraph::root) {
A[a] = b;
continue;
}
if (srcGraph::bccFa[a] != -1) {
t = newGraph::fa[t];
}
bcc[t].change(A[a], b);
A[a] = b;
zkwSegmentTree::modify(newGraph::idxMap[t], bcc[t].maxval);
}
} return 0;
}

codeforces 487E Tourists的更多相关文章

  1. Codeforces 487E Tourists [广义圆方树,树链剖分,线段树]

    洛谷 Codeforces 思路 首先要莫名其妙地想到圆方树. 建起圆方树后,令方点的权值是双联通分量中的最小值,那么\((u,v)\)的答案就是路径\((u,v)\)上的最小值. 然而这题还有修改, ...

  2. UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...

  3. CodeForces 487E Tourists(圆方树+线段树+树链剖分)

    题意 ​ \(n\) 个点 \(m\) 条边的无向连通图,每个点有点权,\(q\) 个要求,每次更新一个点的点权或查询两点间路径权值最小的点最小的路径. 思路 ​ 算是圆方树的板子吧?圆方树处理的主要 ...

  4. Tourists Codeforces - 487E

    https://codeforces.com/contest/487/problem/E http://uoj.ac/problem/30 显然割点走过去就走不回来了...可以看出题目跟点双有关 有一 ...

  5. CF数据结构练习

    1. CF 438D The Child and Sequence 大意: n元素序列, m个操作: 1,询问区间和. 2,区间对m取模. 3,单点修改 维护最大值, 取模时暴力对所有>m的数取 ...

  6. [UOJ30/Codeforces Round #278 E]Tourists

    传送门 好毒瘤的一道题QAQ,搞了好几好几天. UOJ上卡在了53个点,CF上过了,懒得优化常数了 刚看时一眼Tarjan搞个强连通分量然后缩点树链剖分xjb搞搞就行了,然后写完了,然后WA了QAQ. ...

  7. Solution -「CF 487E」Tourists

    \(\mathcal{Description}\)   Link.   维护一个 \(n\) 个点 \(m\) 条边的简单无向连通图,点有点权.\(q\) 次操作: 修改单点点权. 询问两点所有可能路 ...

  8. Codeforces Round #278 (Div. 2)

    题目链接:http://codeforces.com/contest/488 A. Giga Tower Giga Tower is the tallest and deepest building ...

  9. Educational Codeforces Round 21 Problem E(Codeforces 808E) - 动态规划 - 贪心

    After several latest reforms many tourists are planning to visit Berland, and Berland people underst ...

随机推荐

  1. ajax.js

    /**通用ajax服务的定义对象 * services可以是单个服务对象,也可以是service服务数组 * 具体服务的定义请参考appendServices成员函数 */ function Serv ...

  2. php定时自动执行 需启动第一次

    1 2 3 4 5 6 7 8 9 10 11 12 <? ignore_user_abort(); //即使Client断开(如关掉浏览器),PHP脚本也可以继续执行. set_time_li ...

  3. Nginx 之一:编译安装nginx 1.8.1 及配置

    一:基介绍 官网地址www.nginx.org,nginx是由1994年毕业于俄罗斯国立莫斯科鲍曼科技大学的同学为俄罗斯rambler.ru公司开发的,开发工作最早从2002年开始,第一次公开发布时间 ...

  4. Python学习之collections module-defaultdict()

    defaultdict()继承自dict系统内置类型defaultdict()定义以及作用:    返回一个和dictionay类似对象,和dict不同:      1. 可以制定key对应value ...

  5. C语言之ASCII码

    ASCII码 ASCII码值在65~90之间,为大写字母.ASCII码值在97~122之间,为小写字母.ASCII码值在48~57之间,为数字.ASCII码值不在上述3个范围内,为特殊字符.

  6. ASP.NET MVC3 Razor视图引擎-基础语法

    I:ASP.NET MVC3在Visual Studio 2010中的变化 在VS2010中新建一个MVC3项目可以看出与以往的MVC2发生了很明显的变化. 1.ASP.NET MVC3必要的运行环境 ...

  7. Test class should have exactly one public zero-argument constructor

    java.lang.Exception: Test class should have exactly one public zero-argument constructor at org.juni ...

  8. poj3308Paratroopers(最小割)

    题目请戳这里 题目大意:给一个n*m的矩阵,给一些点(ri,ci)表示该点在第ri行第ci列.现在要覆盖所有的点,已知覆盖第i行代价为Ri,覆盖第j列代价为Cj.总代价是累乘的,求最小总代价能覆盖所有 ...

  9. linux下观看b站视频,解决字体乱码

    如图: 各种字体都显示为方块,解决办法也很简单. 点击视频右边的齿轮,也就是设置,更改字体. 默认的微软雅黑字体,一般换成其他的字体应该都能正常显示. 这是为更改后:

  10. Drainage Ditches(最大流)

    Drainage Ditches Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 64044   Accepted: 2471 ...