最近公共祖先
\(\text{hdu-2874}\)
给定一张 \(n\) 个点 \(m\) 条边的有权无向图,对于每个询问 \((x, y)\),输出 \(x,y\) 之间的距离,或报告两个点不连通。
\(2 \le n \le 10^4\),\(0 \le m < 10^4\),\(1 \le q \le 10^6\)。
用并查集判连通性,LCA 求两点的距离,注意细节。
注意:多测要清空 \(fa\) 数组,虽然看似不用清。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 10005
#define pii pair<long long, long long>
#define fi first
#define se second
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, q, dep[MAXN], fa[MAXN][30], lg[MAXN], f[MAXN], a[MAXN];
vector<pii > v[MAXN];
long long find(long long x) { return (f[x] == x) ? x : f[x] = find(f[x]); }
void dfs(long long x, long long ff) {
fa[x][0] = ff, dep[x] = dep[ff] + 1;
for(int i = 1; i <= lg[dep[x]]; i ++)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(auto it : v[x]) if(it.fi != ff)
a[it.fi] = a[x] + it.se, dfs(it.fi, x);
return;
}
long long lca(long long x, long long y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y])
x = fa[x][lg[dep[x] - dep[y]] - 1];
if(x == y) return x;
for(int i = lg[dep[x]] - 1; i >= 0; i --)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
for(int i = 1; i < MAXN; i ++) lg[i] = lg[i >> 1] + 1;
while(cin >> n >> m >> q) {
for(int i = 1; i <= n; i ++)
f[i] = i, v[i].clear(), a[i] = dep[i] = 0;
memset(fa, 0, sizeof fa);
for(int i = 1; i <= m; i ++) {
long long x, y, w; cin >> x >> y >> w;
v[x].push_back({y, w}), v[y].push_back({x, w});
f[find(x)] = find(y);
}
for(int i = 1; i <= n; i ++) if(!dep[i]) dfs(i, 0);
while(q --) {
long long x, y; cin >> x >> y;
if(find(x) != find(y)) { cout << "Not connected\n"; continue; }
cout << a[x] + a[y] - 2 * a[lca(x, y)] << "\n";
}
}
return 0;
}
\(\text{hdu-3078}\)
给定一棵树,包含 \(n\) 个节点,每个点有权值 \(a_i\),有以下 \(q\) 次操作:
0 x y表示将 \(a_x\) 修改为 \(y\)。k x y表示询问 \(x \to y\) 路径上第 \(k\) 大的点权。
对于第二个操作,若经过的点少于 \(k\) 个,则报告 invalid request!。
\(0 \le n \le 8 \times 10^4\),\(0 \le q \le 3 \times 10^4\)。
非常神秘的一道题,貌似数据太水了。
直接暴力把路径上的点存起来,然后排序就好了,但显然没法通过这样的数据范围。
但就是过了 \(\dots\)
确实不太知道如果数据强度够的话怎么写,大概是述链剖分吧。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 80005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, q, a[MAXN], b[MAXN], dep[MAXN], fa[MAXN][30], lg[MAXN];
vector<long long> v[MAXN];
void dfs(long long x, long long f) {
fa[x][0] = f, dep[x] = dep[f] + 1;
for(int i = 1; i <= lg[dep[x]]; i ++)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(auto y : v[x]) if(y != f) dfs(y, x);
return;
}
long long lca(long long x, long long y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y])
x = fa[x][lg[dep[x] - dep[y]] - 1];
if(x == y) return x;
for(int i = lg[dep[x]] - 1; i >= 0; i --)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i < n; i ++) {
long long x, y, w; cin >> x >> y;
v[x].push_back(y), v[y].push_back(x);
}
for(int i = 1; i < MAXN; i ++) lg[i] = lg[i >> 1] + 1;
dfs(1, 0);
while(q --) {
long long k, x, y, z, cnt = 0;
cin >> k >> x >> y;
if(!k) { a[x] = y; continue; }
z = lca(x, y);
for(int i = x; i != z; i = fa[i][0]) b[++ cnt] = a[i];
for(int i = y; i != z; i = fa[i][0]) b[++ cnt] = a[i];
b[++ cnt] = a[z];
if(cnt < k) { cout << "invalid request!\n"; continue; }
sort(b + 1, b + cnt + 1);
cout << b[cnt - k + 1] << "\n";
}
return 0;
}
\(\text{hdu-3830}\)
小 X、小 Y 和小 Z 正在玩跳棋,这时小 Y 感到烦躁。他想把棋盘做得更大一些。虽然小 Z 坚持原版,但小 X 支持小 Y。他们把棋盘放大后,棋盘变成了一个无限线段。
现在棋盘就像是数轴,每个整数点都可以放一个棋子。初始状态下有三个不同整数点上各有一个棋子,游戏进行时,始终有三个棋子。每次,他们可以选择一个棋子 A 跳过一个中间棋子B到一个新位置(但是旧 A 和 B 之间的距离等于新A和B之间的距离,并且在[旧 A, 新 A]范围内除了 B 以外没有其他棋子)。
玩了一会儿后,他们想知道是否可以遵循规则,将给定状态 \(a,b,c\) 转移到 \(x,y,z\)。由于棋子被视为相同,\(a\) 必须跳到 \(x\) 是不必要的。
\(-10^9 \le a,b,c,x,y,z \le 10^9\)。
以下部分题解来自于 HDU-3830 Checkers - dgsvygd - 博客园。
隐式建图
考虑对排序好的 \(a,b,c\) 分类讨论:
- 如果 \(b−a = c−b\):只有两种情况转移,中间的往左边或者右边跳
- 如果 \(b−a \ne c−b\):除了上述的情况,还有两边通过中间跳
第二种情况显然可以被认为是其上一个状态,通过中间那个跳珠往两边跳造成的
把一个局面视为一个节点,我们则可以认为,如果中间往左边跳,会产生一个左子节点的状态;中间往右边跳,会产生一个右子节点的状态;如果是两边往中间跳,相等于往父节点走了一步;根节点为上述的情况 \(1\)。
因此如果两个状态是可互相到达,说明他们的根是相同的。最短路径就是找他们的树上最短路径,即求解 lca。倍增计算的时候就不要建图了,构建一个函数来算他的 \(k\) 级祖先,通过整除的方式加速。
其实这种方式还挺好用的,不需要建图,但实际上有图的模型。
注意:本题多测,但题面没说,很阴。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define vc vector<long long>
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
vc v[2];
long long calc(vc &a, long long k) {
long long res = 0, &x = a[0], &y = a[1], &z = a[2];
while(k && (y - x) != (z - y)) {
long long l = y - x, r = z - y, t = 0;
if(l < r) t = min((r - 1) / l, k), x += t * l, y += t * l;
else t = min((l - 1) / r, k), y -= t * r, z -= t * r;
k -= t, res += t;
}
return res;
}
long long lca(vc a, vc b) {
vc ta = a, tb = b;
long long depa = calc(ta, 1e18);
long long depb = calc(tb, 1e18);
if(ta != tb) return -1;
if(depa < depb) swap(a, b), swap(depa, depb);
long long res = depa - depb;
depa -= calc(a, res);
if(a == b) return res;
for(int i = 40; i >= 0; i --) {
vc pa = a, pb = b;
long long t = calc(pa, 1ll << i);
calc(pb, 1ll << i);
if(pa != pb) a = pa, b = pb, res += (t << 1ll);
}
return res + 2;
}
int main() {
long long x, y, z;
while(cin >> x >> y >> z) {
v[0].clear(), v[1].clear();
v[0].push_back(x), v[0].push_back(y);
v[0].push_back(z);
for(int i = 1; i <= 3; i ++) {
long long p; cin >> p;
v[1].push_back(p);
}
sort(v[0].begin(), v[0].end());
sort(v[1].begin(), v[1].end());
long long ans = lca(v[0], v[1]);
if(ans == -1) cout << "NO\n";
else cout << "YES\n" << ans << "\n";
}
return 0;
}
\(\text{hdu-4338}\)
给定 \(n\) 个点 \(m\) 条边的无向图,可能有环也可能不连通。有 \(q\) 次询问,每次询问给定 \(x,y\),求 \(x\) 到 \(y\) 的所有路径中,一定不经过的点的个数,这里的路径指简单路径,即一个点和一条边至多经过一次形成的路径。
若 \(x,y\) 之间不存在简单路径,则输出 \(n\)。
注意:本题点的编号范围是 \(0 \sim n - 1\)。
\(1 \le n \le 10^5\),\(0 \le m \le 2 \times 10^5\),\(1 \le q \le 10^5\)。
非常非常毒瘤的一道题,花了我两个多小时,结果被卡常了,于是就不调了。
其实思路挺简单的。
首先原图可能有环,考虑把点双连通分量缩成一个点,但是仅把点双连边建图的话显然会出问题。比如,三个点双中间有一个割点,那么建图后三个点双会形成一条链,若此时询问最左侧点双和最右侧点双内的点,那么此时会认为三个点双内的点都能经过,但实际上只能经过割点而已,不能经过最中间点双内除割点以外的点,所以这样建图就出问题了。
通过反例我们可以发现,只需要把割点取出来,连接割点所在的所有点双,就解决了。
其实这样的图就是圆方树。其中割点为方点,点双为圆点。但是我写的时候并没有意识到,等发现的时候已经晚了,于是成功调了两个多小时,无果。
询问时若询问两个点,先把这两个点对应到缩点后的图上,然后 LCA 求出两个点之间路径经过的点数,这个需要推一下公式。下面是公式的全貌:
我们记缩点后点的大小为 \(sz_i\),点到根节点路径上经过的点数为 \(sm_i\),点到根节点的距离(也就是深度)为 \(dep_i\),\(x,y\) 的最近公共祖先为 \(z\)。实际上这个公式就是用容斥推出来的。
除上述数据之外,重新建图的过程中我们还需要记录每个点对应新图上的点的编号。
还有一些特殊情况需要特判:
- \(x = y\),输出 \(n-1\)。
- \(x,y\) 存在不是 \(0 \sim n - 1\) 的数,即 \(x \ge n\) 或 \(y \ge n\),输出 \(n\)。
- \(x,y\) 不连通,输出 \(n\)。这一情况需要用并查集判。
- \(x,y\) 在一个点双中,输出 \(n-sz_p\),其中 \(p\) 为 \(x,y\) 所在的点双编号。
以及,这道题不能用邻接表存图,而且重构图可能会有 \(\ge n\) 个点,所以数组要开大点。
注意常数。
下面是一份没通过的代码,但是逻辑大概都是对的,卡常的问题。
以及代码最下面有两个较强的手捏样例。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
using namespace std;
#define MAXN 200005
int read() {
int x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
int n, m, q, cs, dfn[MAXN], low[MAXN], dn, s[MAXN], is[MAXN], sm[MAXN];
int fa[MAXN][30], lg[MAXN], dep[MAXN], sz[MAXN], f[MAXN], mp[MAXN];
vector<int> v[MAXN], g[MAXN];
vector<vector<int> > scc;
stack<int> st;
bool gd[MAXN];
int find(int x) { return (f[x] == x) ? x : f[x] = find(f[x]); }
void tarjan(int x, bool fp) {
dfn[x] = low[x] = ++ dn;
int son = 0; st.push(x);
if(v[x].empty() && fp) { scc.push_back({x}); return; }
for(auto y : v[x])
if(!dfn[y]) {
tarjan(y, 0), low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]) {
son ++;
if(!fp || son >= 2) gd[x] = 1;
int t = 0; scc.push_back({});
do {
t = st.top(), st.pop(), scc.back().push_back(t);
} while(t != y);
scc.back().push_back(x);
}
}
else low[x] = min(low[x], dfn[y]);
return;
}
void dfs(int x, int ff) {
fa[x][0] = ff, dep[x] = dep[ff] + 1;
sm[x] = sm[ff] + sz[x];
for(int i = 1; i <= lg[dep[x]]; i ++)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(auto y : g[x]) if(y != ff) dfs(y, x);
return;
}
int lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y])
x = fa[x][lg[dep[x] - dep[y]] - 1];
if(x == y) return x;
for(int i = lg[dep[x]] - 1; i >= 0; i --)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
for(int i = 1; i < MAXN; i ++) lg[i] = lg[i >> 1] + 1;
while(cin >> n >> m) {
for(int i = 1; i <= n; i ++)
v[i].clear(), g[i].clear(), sz[i] = sm[i] = 0;
memset(fa, 0, sizeof fa);
memset(mp, 0, sizeof mp);
memset(dep, 0, sizeof dep);
memset(dfn, 0, sizeof dfn); scc.clear();
memset(low, 0, sizeof low); dn = 0;
while(!st.empty()) st.pop();
for(int i = 1; i <= n; i ++) f[i] = i;
for(int i = 1; i <= m; i ++) {
int x, y; cin >> x >> y; x ++, y ++;
v[x].push_back(y), v[y].push_back(x);
f[find(x)] = find(y);
}
for(int i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i, 1);
int res = 0;
for(auto it : scc) {
sz[++ res] = it.size();
int id = res;
for(auto x : it)
if(gd[x]) {
if(!mp[x]) mp[x] = (++ res);
g[id].push_back(mp[x]);
g[mp[x]].push_back(id);
sz[mp[x]] = 1;
}
else mp[x] = id;
}
for(int i = 1; i <= res; i ++) if(!dep[i]) dfs(i, 0);
cin >> q; cout << "Case #" << (++ cs) << ":\n";
while(q --) {
int x, y, z; cin >> x >> y;
if(x == y) { cout << n - 1 << "\n"; continue; }
if(x >= n || y >= n) { cout << n << "\n"; continue; }
x ++, y ++;
if(find(x) != find(y)) {
cout << n << "\n";
continue;
}
x = mp[x], y = mp[y];
if(x == y) { cout << n - sz[x] << "\n"; continue; }
z = lca(x, y);
int p1 = sm[x] + sm[y] - 2 * sm[z] + sz[z];
int p2 = dep[x] + dep[y] - 2 * dep[z];
cout << n - (p1 - p2) << "\n";
}
cout << "\n";
}
return 0;
}
/*
11 11
0 1
0 2
1 2
1 3
1 4
2 5
2 6
7 8
7 9
8 9
8 10
5
1 2
3 5
3 4
0 3
7 10
9 12
0 1
0 2
1 2
2 3
2 4
3 4
3 5
3 6
5 6
4 7
4 8
7 8
6
0 2
0 3
0 5
2 5
3 5
5 6
*/
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19489086

浙公网安备 33010602011771号