做题记录 230328 // LCA
今日笑料:菌菌是在为自己辩护!
A. 暗的连锁
http://222.180.160.110:1024/contest/3470/problem/1
不难发现树上的边和附加边是两个独立的部分。
若只看所有附加边,对于某一条附加边,当它是这些附加边中的桥时,切断它所连接的两点在树上的简单路径即可达到目的。
于是我深陷于这个思路想了很久。最后 GM 告诉我们了一种新的思路:对于附加边,将 组成 它所连接的两点在树上的简单路径 的边 的权值统一加一,最后遍历每条树边:若未被标记过,则说明切掉这条边后,图已经分为两部分,答案加上附加边总数;若只被标记过一次,则贡献为标记它的那条附加边,即贡献为 1。
将边权下移至点权,则问题转化为树剖 / 树上差分。这里我用的是更难写的树剖(因为好久没写过了怕忘了)。
事实上也确实忘了。有一行判断完全写错了。紫菜!
namespace XSC062 {
#define lt (p << 1)
#define rt (lt | 1)
using namespace fastIO;
const int maxn = 1e5 + 5;
struct _ {
int u, d;
int l, r;
};
_ t[maxn << 2];
std::vector<int> g[maxn];
int n, m, x, y, now, res, ans;
int siz[maxn], son[maxn], fa[maxn];
int top[maxn], dfn[maxn], tab[maxn], dep[maxn];
inline void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
inline void pushup(int p) {
t[p].u = t[lt].u + t[rt].u;
return;
}
inline void pushdown(int p) {
if (t[p].d) {
t[lt].d += t[p].d;
t[rt].d += t[p].d;
t[lt].u += (t[lt].r - t[lt].l + 1) * t[p].d;
t[rt].u += (t[rt].r - t[rt].l + 1) * t[p].d;
t[p].d = 0;
}
return;
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r)
return;
int mid = (l + r) >> 1;
bld(lt, l, mid);
bld(rt, mid + 1, r);
return;
}
void upd(int p, int l, int r, int v) {
if (l <= t[p].l && t[p].r <= r) {
t[p].d += v;
t[p].u += (t[p].r - t[p].l + 1) * v;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
upd(lt, l, r, v);
if (r > mid)
upd(rt, l, r, v);
pushup(p);
return;
}
int qry(int p, int x) {
if (t[p].l == t[p].r)
return t[p].u;
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid)
return qry(lt, x);
return qry(rt, x);
}
void DFS1(int x, int f) {
siz[x] = 1, fa[x] = f;
for (auto i : g[x]) {
if (i == f)
continue;
dep[i] = dep[x] + 1;
DFS1(i, x);
siz[x] += siz[i];
if (siz[i] > siz[son[x]])
son[x] = i;
}
return;
}
void DFS2(int x, int t) {
top[x] = t;
dfn[x] = ++now;
tab[now] = x;
if (son[x])
DFS2(son[x], t);
for (auto i : g[x]) {
if (i == fa[x])
continue;
if (i != son[x])
DFS2(i, i);
}
return;
}
inline void upd(int x, int y, int v) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
upd(1, dfn[top[x]], dfn[x], v);
x = fa[top[x]];
}
if (dep[x] < dep[y])
swap(x, y);
upd(1, dfn[y], dfn[x], v);
upd(1, dfn[y], dfn[y], -v);
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
int main() {
read(n), read(m);
for (int i = 1; i < n; ++i) {
read(x), read(y);
add(x, y), add(y, x);
}
dep[1] = 1;
DFS1(1, -1), DFS2(1, 1);
bld(1, 1, n);
for (int i = 1; i <= m; ++i) {
read(x), read(y);
upd(x, y, 1);
}
for (int i = 2; i <= n; ++i) {
res = qry(1, dfn[i]);
if (res == 0)
ans += m;
else if (res == 1)
++ans;
}
print(ans);
return 0;
}
} // namespace XSC062
B. 运输计划
http://222.180.160.110:1024/contest/3470/problem/2
完全不想看题面!题目是什么意思呢?给定 \(m\) 个点对和一个 \(n\) 个节点的树,在树中任选一条边使其权值为 \(0\),问 \(m\) 个点对构成的简单路径权值最大者的最小值。
看到最大的最小,考虑二分。二分一个最小值 mid
,不难想到在权值大于 mid
的路径中寻找最大公共边,若最长路径减去最大公共边小于等于 mid
,则返回 true
。
那这个寻找公共边的部分怎么实现呢?用一个 树剖 / 树上差分 就好了。因为这题卡常,所以我们写树剖(???)
还有一个点就是不难发现大于 mid
的最长路径就是整个路径集合中的最长路径(废话啊这)。
树剖卡卡校 OJ 上 3s 能过!
namespace XSC062 {
#define lt (p << 1)
#define rt (lt | 1)
using namespace fastIO;
const int maxn = 3e5 + 5;
struct _ {
int u, d;
int l, r;
};
struct __ {
int v, w;
__() {}
__(int v1, int w1) {
v = v1, w = w1;
}
};
_ t[maxn << 2];
int l, r, mid, mx;
std::vector<__> g[maxn];
int n, m, x, y, w, now, res;
int u[maxn], v[maxn], q[maxn], dis[maxn];
int siz[maxn], son[maxn], fa[maxn], val[maxn];
int top[maxn], dfn[maxn], tab[maxn], dep[maxn];
inline void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
inline void pushup(int p) {
t[p].u = t[lt].u + t[rt].u;
return;
}
inline void pushdown(int p) {
if (t[p].d) {
t[lt].d += t[p].d;
t[rt].d += t[p].d;
t[lt].u += (t[lt].r - t[lt].l + 1) * t[p].d;
t[rt].u += (t[rt].r - t[rt].l + 1) * t[p].d;
t[p].d = 0;
}
return;
}
void bld(int p, int l, int r) {
t[p].u = t[p].d = 0;
t[p].l = l, t[p].r = r;
if (l == r)
return;
int mid = (l + r) >> 1;
bld(lt, l, mid);
bld(rt, mid + 1, r);
return;
}
void upd(int p, int l, int r, int v) {
if (l <= t[p].l && t[p].r <= r) {
t[p].d += v;
t[p].u += (t[p].r - t[p].l + 1) * v;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
upd(lt, l, r, v);
if (r > mid)
upd(rt, l, r, v);
pushup(p);
return;
}
int qry(int p, int x) {
if (t[p].l == t[p].r)
return t[p].u;
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid)
return qry(lt, x);
return qry(rt, x);
}
void DFS1(int x, int f) {
siz[x] = 1, fa[x] = f;
for (auto i : g[x]) {
if (i.v == f)
continue;
dep[i.v] = dep[x] + 1;
dis[i.v] = dis[x] + i.w;
val[i.v] = i.w;
DFS1(i.v, x);
siz[x] += siz[i.v];
if (siz[i.v] > siz[son[x]])
son[x] = i.v;
}
return;
}
void DFS2(int x, int t) {
top[x] = t;
dfn[x] = ++now;
tab[now] = x;
if (son[x])
DFS2(son[x], t);
for (auto i : g[x]) {
if (i.v == fa[x])
continue;
if (i.v != son[x])
DFS2(i.v, i.v);
}
return;
}
inline void upd(int x, int y, int v) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
upd(1, dfn[top[x]], dfn[x], v);
x = fa[top[x]];
}
if (dep[x] < dep[y])
swap(x, y);
upd(1, dfn[y], dfn[x], v);
upd(1, dfn[y], dfn[y], -v);
return;
}
inline int LCA(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
x = fa[top[x]];
}
if (dep[x] < dep[y])
swap(x, y);
return y;
}
inline void add(int x, int y, int w) {
g[x].push_back(__(y, w));
return;
}
inline bool check(int x) {
bld(1, 1, n);
int cnt = 0, t = -1;
for (int i = 1; i <= m; ++i) {
if (q[i] > x) {
++cnt;
upd(u[i], v[i], 1);
}
}
for (int i = 2; i <= n; ++i) {
if (qry(1, dfn[i]) == cnt)
t = max(t, val[i]);
}
return q[mx] - t <= x;
}
int main() {
read(n), read(m);
for (int i = 1; i < n; ++i) {
read(x), read(y), read(w);
add(x, y, w), add(y, x, w);
r += w;
}
dep[1] = 1;
DFS1(1, -1), DFS2(1, 1);
bld(1, 1, n);
for (int i = 1; i <= m; ++i) {
read(u[i]), read(v[i]);
q[i] = dis[u[i]] + dis[v[i]] -
2 * dis[LCA(u[i], v[i])];
if (q[i] > q[mx])
mx = i;
}
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
res = mid;
r = mid - 1;
}
else l = mid + 1;
}
print(res);
return 0;
}
} // namespace XSC062
可惜洛谷上是 2s,\(\mathcal O(n\log^2n \log V)\) 过不了啊,但是 \(\mathcal O(n\log n\log V)\) 的树上差分就完全没有问题。恨啊!
namespace XSC062 {
using namespace fastIO;
const int maxn = 3e5 + 5;
const int LEN = (1 << 20);
struct __ {
int v, w;
__() {}
__(int v1, int w1) {
v = v1, w = w1;
}
};
int cnt[maxn];
int l, r, mid, mx;
int n, m, x, y, w, res;
std::vector<__> g[maxn];
int top[maxn], dep[maxn];
int u[maxn], v[maxn], q[maxn], dis[maxn];
int siz[maxn], son[maxn], fa[maxn], val[maxn];
inline void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
void DFS1(int x, int f) {
siz[x] = 1, fa[x] = f;
for (auto i : g[x]) {
if (i.v == f)
continue;
dep[i.v] = dep[x] + 1;
dis[i.v] = dis[x] + i.w;
val[i.v] = i.w;
DFS1(i.v, x);
siz[x] += siz[i.v];
if (siz[i.v] > siz[son[x]])
son[x] = i.v;
}
return;
}
void DFS2(int x, int t) {
top[x] = t;
if (son[x])
DFS2(son[x], t);
for (auto i : g[x]) {
if (i.v == fa[x])
continue;
if (i.v != son[x])
DFS2(i.v, i.v);
}
return;
}
inline void DFS3(int x) {
for (auto i : g[x]) {
if (i.v == fa[x])
continue;
DFS3(i.v);
cnt[x] += cnt[i.v];
}
return;
}
inline int LCA(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
x = fa[top[x]];
}
if (dep[x] < dep[y])
swap(x, y);
return y;
}
inline void add(int x, int y, int w) {
g[x].push_back(__(y, w));
return;
}
inline bool check(int x) {
memset(cnt, 0, (n + 1) * sizeof (int));
int tot = 0, t = -1;
for (int i = 1; i <= m; ++i) {
if (q[i] > x) {
++tot;
++cnt[u[i]], ++cnt[v[i]];
cnt[LCA(u[i], v[i])] -= 2;
}
}
DFS3(1);
for (int i = 2; i <= n; ++i) {
if (cnt[i] == tot)
t = max(t, val[i]);
}
return q[mx] - t <= x;
}
int main() {
read(n), read(m);
for (int i = 1; i < n; ++i) {
read(x), read(y), read(w);
add(x, y, w), add(y, x, w);
r += w;
}
dep[1] = 1;
DFS1(1, -1), DFS2(1, 1);
for (int i = 1; i <= m; ++i) {
read(u[i]), read(v[i]);
q[i] = dis[u[i]] + dis[v[i]] -
2 * dis[LCA(u[i], v[i])];
if (q[i] > q[mx])
mx = i;
}
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
res = mid;
r = mid - 1;
}
else l = mid + 1;
}
print(res);
return 0;
}
} // namespace XSC062
树刨当然是极好的!假的。
C. 天天爱跑步
http://222.180.160.110:1024/contest/3470/problem/3
好难好难好难!看不懂!
—— · EOF · ——
真的什么也不剩啦 😖