如果不是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. BlogUI的使用

  2. js实现input输入框只能输入数字的功能

    <input type="text" style="ime-mode:disabled;" onpaste="return false;&quo ...

  3. Aptana Studio 3 官方汉化包汉化

    Babel Language Pack Update Site for Helios This URL is an Eclipse software repository:http://downloa ...

  4. Linux系统目录(转载)

    /         (这就是著名的根)├── bin         (你在终端运行的大多数程序,比如cp.mv...)├── boot         (内核放在这里,这个目录也经常被作为某个独立分 ...

  5. Ruby学习: 全局变量 和常量

    一.全局变量 ruby中支持全局变量,ruby中的全局变量有如下几个特点: 1)标识符以$开头标记 2)全局变量可以在程序的任何地方加以引用 3)全局变量无需提前进行变量声明.引用尚未初始化的全局变量 ...

  6. github的.md格式文件

    md文件是github改良了markdown的语法,用来显示在项目首页的文件.在官方的网址说的很清楚: GitHub uses what we're calling "GitHub Flav ...

  7. poj 3281 Dining 网络流-最大流-建图的题

    题意很简单:JOHN是一个农场主养了一些奶牛,神奇的是这些个奶牛有不同的品味,只喜欢吃某些食物,喝某些饮料,傻傻的John做了很多食物和饮料,但她不知道可以最多喂饱多少牛,(喂饱当然是有吃有喝才会饱) ...

  8. MFC中SDI程序创建流程的回顾

    SDI程序创建流程的回顾 从CWinApp.InitialInstance()开始, 1.首先应用程序对象创建文档模板; CSingleDocTemplate* pDocTemplate; pDocT ...

  9. Android 开发之网易云音乐(或QQ音乐)的播放界面转盘和自定义SeekBar的实现

    这个东西我在eoeAndroid上首发的,但没有详细的实现说明:http://www.eoeandroid.com/thread-317901-1-1.html 在csdn上进行详细的说明吧.(同时上 ...

  10. Centos6.4下tar包安装最新版Mysql5.6

    1.下载 mysql:http://www.mysql.com/downloads/ (须要注冊ORACLE账号) 版本号:mysql-advanced-5.6.21-linux-glibc2.5-x ...