P6643 [CCO 2020] Mountains and Valleys
首先答案上界是 \(2n-2-D\),其中 \(D\) 是直径长度。显然非树边至多走两条。
但是实际上非树边只会走一条,因为对于一条边 \((u,v,w)\),如果 \(w\ge d(u,v)\) 就不优,所以此时有 \(D>w\),即 \(D\ge\lceil\frac{n}{3}\rceil+1\)。那么走两条边的代价至少 \(n-3+2\lceil\frac{n}{3}\rceil\ge 2n-2-\lceil\frac{n}{3}\rceil-1\),于是也是不优的。
那么考虑枚举这条边 \((u,v,w)\),那么对于原树的情况,我们会选一条路径 \(s\to t\) 只走一次,剩下的边走两次。加入 \((u,v,w)\) 后,考虑 \(u,v\) 在路径上投影 \(p,q\),如果 \(p=q\) 那么 \(u\to v\) 路径上都可以省一次。否则 \(u\to p,v\to q\) 可以省一次,但是 \(p\to q\) 要多走一次,注意我们可以少走一条边,即新的代价为 \(2n-2-d(s,t)-d(u,v)+2d(p,q)+w-2[p\neq q]\)。
这个东西可以考虑把 \(u\to v\) 路径上边权翻成 \(-1\),那么 \(s\to t\) 就是要找一条直径。注意要分别统计含 \(-1\) 和不含的情况。可以换根做到总复杂度 \(O(nm)\)。
考虑进一步优化。\(p=q\) 是简单的,此时相当于断掉 \(u\to v\) 路径所有边后,找剩余连通块直径的 \(\max\)。这个可以倍增预处理。
对于 \(p\neq q\),可以发现 \(d(s,t)+d(u,v)-2d(p,q)=d(s,u)+d(t,v)\)。我们要最小化 \(2n-4-d(s,u)-d(t,v)+w\),这个需要 \(<2n-2-D\),即有 \(D+w\le d(s,u)+d(t,v)+1\)。
因为 \(w\ge \lceil\frac{n}{3}\rceil\),而 \(d(s,u),d(t,v)\le D\),所以有 \(d(s,u),d(t,v)\ge w-1\ge \lceil\frac{n}{3}\rceil-1\)。同时因为 \(d(u,v)>w\),也有 \(d(u,v)\ge \lceil\frac{n}{3}\rceil+1\)。
我们取一条边 \((x,y)\),并分析其和 \(s,t,u,v\) 的关系。我们希望它在 \(p\to q\) 路径上。因为 \(d(s,u),d(t,v)\ge\lceil\frac{n}{3}\rceil-1\),因此两边直径点数应该 \(\ge\lceil\frac{n}{3}\rceil\),而且这个下界不能同时取到,因为 \(D+w\le d(s,u)+d(t,v)+1\),所以两边的直径点数之和 \(\ge D+w+1\ge 2\lceil\frac{n}{3}\rceil+2\)。
因此首先不可能四个点都在一侧,否则这边子树大小 \(\ge D+w+1\),而另一边又有 \(\lceil\frac{n}{3}\rceil\) 矛盾。所以两侧都至少有一个点。并且对于一侧,不可能恰好有 \(s,t\) 或恰好有 \(u,v\),否则就不交了。
因此如果不满足 \((x,y)\) 在 \(p\to q\) 上,那就只能一侧恰好有一个点,不妨设 \(x\) 方向有 \(s\)。那么 \(d(s,u)=d(s,x)+1+d(y,u)\),即需要 \(D+w-d(s,x)\le d(y,u)+d(t,v)+2\)。
进一步放缩 \(D-d(s,x)\),考虑把 \(D\) 放缩成以 \(s\) 为起点,过 \((x,y)\) 的最长路,那么 \(D-d(s,x)\) 就是这条路径在 \(y\) 侧的部分再 \(+1\),而我们知道 \(v,t\) 都在 \(y\) 侧,且 \(d(v,t)\ge \lceil\frac{n}{3}\rceil-1\),所以 \(\max(d(v,y),d(t,y))\ge \lceil\frac{d(v,t)}{2}\rceil\ge \lceil\frac{n-3}{6}\rceil\)。
因此 \(D-d(s,x)\ge \max(d(v,y),d(t,y))+1\ge \lceil\frac{n}{6}\rceil\),带回就有 \(w+\lceil\frac{n}{6}\rceil\le d(y,u)+d(t,v)+2\)。而 \(w\ge \lceil\frac{n}{3}\rceil\),因此 \(d(y,u)+d(t,v)+2\ge \lceil\frac{n}{2}\rceil\)。
这说明,不妨设 \(x\) 一侧只有一个点,那么另一侧子树大小 \(\ge \lceil\frac{n}{2}\rceil\)。考虑换重心 \(root\) 为根,取出子树最大的两个子节点 \(a,b\),然后我们只对 \(a\to root\),\(b\to root\) 两条边,把它当作 \((x,y)\) 统计答案。
其实这就足够了,因为如果 \(a\to root\) 没统计上,那么就有一个点在 \(a\) 子树内,同时 \(b\to root\) 没统计上就有一个点在 \(b\) 子树。那么此时交的路径只能从 \(root\) 往剩下的点延申,而剩下的点子树大小 \(\le \lceil\frac{n}{3}\rceil-1\),和之前的要求矛盾。
枚举边之后只要两边分别找直径算,总复杂度加上前面的倍增是 1log。
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
template <typename T>
bool Chkmax(T &x, T y) { return (x < y) ? (x = y, 1) : 0; }
template <typename T>
bool Chkmin(T &x, T y) { return (x > y) ? (x = y, 1) : 0; }
const int kN = 5e5 + 5, kM = 2e6 + 5, kS = kN * 4, kLog = 22;
int n, m, e, tot;
struct Edge {
int u, v, w;
} edge[kM];
vector<int> g[kN];
int fa[kN];
int dfn[kN], rev[kN], dep[kN], siz[kN];
struct ST {
int mn[kLog][kN];
int Min(int x, int y) { return dfn[x] < dfn[y] ? x : y; }
void Build() {
for(int i = 1; i < kLog; i++) {
for(int j = 1; j + (1 << i) - 1 <= n; j++) {
mn[i][j] = Min(mn[i - 1][j], mn[i - 1][j + (1 << i - 1)]);
}
}
}
int LCA(int x, int y) {
if(x == y) return x;
x = dfn[x];
y = dfn[y];
if(x > y) swap(x, y);
int p = __lg(y - x++);
return Min(mn[p][x], mn[p][y - (1 << p) + 1]);
}
int Dist(int x, int y) {
return dep[x] + dep[y] - 2 * dep[LCA(x, y)];
}
} st;
struct Diam {
int x, y, d;
Diam() { x = y = 0, d = -1; }
Diam(int _x, int _y, int _d) { x = _x, y = _y, d = _d; }
} ;
Diam operator + (Diam x, Diam y) {
if(x.d < 0) return y;
if(y.d < 0) return x;
int s = 0, t = 0, d = -1;
if(Chkmax(d, x.d)) s = x.x, t = x.y;
if(Chkmax(d, y.d)) s = y.x, t = y.y;
for(int i : {x.x, x.y}) for(int j : {y.x, y.y}) {
if(Chkmax(d, st.Dist(i, j))) s = i, t = j;
}
return Diam(s, t, d);
}
struct SGT {
int T;
Diam diam[kS];
void Build() {
for(T = 1; T <= n + 2; T <<= 1) ;
for(int i = 1; i <= n; i++) diam[i + T] = Diam(rev[i], rev[i], 0);
for(int i = T - 1; i; i--) diam[i] = diam[i << 1] + diam[i << 1 | 1];
}
Diam Query(int l, int r) {
Diam ans;
for(l += T, r += T + 1; l < r; l >>= 1, r >>= 1) {
if(l & 1) ans = ans + diam[l++];
if(r & 1) ans = ans + diam[--r];
}
return ans;
}
} sgt;
Diam pre[kN], suf[kN];
int w[kN], s[kN], sub[kN];
int jp[kN];
void DFS(int x, int fa) {
::fa[x] = fa;
dfn[x] = ++tot;
rev[tot] = x;
dep[x] = dep[fa] + 1;
siz[x] = 1;
st.mn[0][tot] = fa;
for(int to : g[x]) {
if(to != fa) {
DFS(to, x);
siz[x] += siz[to];
}
}
}
bool IsAnc(int x, int y) {
return (dfn[x] <= dfn[y]) && (dfn[y] < dfn[x] + siz[x]);
}
void DFS2(int x, int fa) {
if(fa) w[x] = (sgt.Query(dfn[fa], dfn[x] - 1) + sgt.Query(dfn[x] + siz[x], dfn[fa] + siz[fa] - 1)).d;
if(jp[jp[fa]] && (dep[jp[jp[fa]]] + dep[fa] == 2 * dep[jp[fa]])) {
jp[x] = jp[jp[fa]];
s[x] = max({w[x], s[fa], s[jp[fa]]});
} else {
jp[x] = fa;
s[x] = w[x];
}
sub[x] = sgt.Query(dfn[x], dfn[x] + siz[x] - 1).d;
for(int to : g[x]) {
if(to != fa) DFS2(to, x);
}
}
int Root(int x, int fa) {
int mx = n - siz[x];
for(int to : g[x]) {
if(to == fa) continue;
if(int tmp = Root(to, x)) return tmp;
Chkmax(mx, siz[to]);
}
return (mx * 2 <= n) * x;
}
int Query(int s, int t) {
if(s == t) return sgt.diam[1].d;
if(dep[s] < dep[t]) swap(s, t);
int ans = 0;
if(IsAnc(t, s)) {
Chkmax(ans, sub[s]);
int p = fa[s], v = dep[t] + 1;
while(dep[p] > v) {
if(dep[jp[p]] >= v) Chkmax(ans, ::s[p]), p = jp[p];
else Chkmax(ans, ::w[p]), p = fa[p];
}
Chkmax(ans, (pre[dfn[p] - 1] + suf[dfn[p] + siz[p]]).d);
return ans;
}
Chkmax(ans, sub[s]);
Chkmax(ans, sub[t]);
while(dep[s] > dep[t]) {
if(dep[jp[s]] >= dep[t]) Chkmax(ans, ::s[s]), s = jp[s];
else Chkmax(ans, ::w[s]), s = fa[s];
}
while(fa[s] != fa[t]) {
if(jp[s] != jp[t]) {
Chkmax(ans, max(::s[s], ::s[t]));
s = jp[s], t = jp[t];
} else {
Chkmax(ans, max(::w[s], ::w[t]));
s = fa[s], t = fa[t];
}
}
int lca = fa[s];
if(dfn[s] > dfn[t]) swap(s, t);
Diam tmp = pre[dfn[s] - 1] + suf[dfn[t] + siz[t]];
tmp = tmp + sgt.Query(dfn[s] + siz[s], dfn[t] - 1);
Chkmax(ans, tmp.d);
return ans;
}
int nsz[kN];
void DFS3(int x, int fa) {
nsz[x] = 1;
for(int to : g[x]) {
if(to != fa) {
DFS3(to, x);
nsz[x] += nsz[to];
}
}
}
int Solve(int x, int y) {
if(fa[y] == x) swap(x, y);
Diam in = sgt.Query(dfn[x], dfn[x] + siz[x] - 1);
Diam out = pre[dfn[x] - 1] + suf[dfn[x] + siz[x]];
int ans = inf;
for(int i = 1; i <= e; i++) {
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
if(w > n) continue;
bool fu = IsAnc(x, u), fv = IsAnc(x, v);
if(fu == fv) continue;
if(!fu && fv) swap(u, v), swap(fu, fv);
int sum = max(st.Dist(u, in.x), st.Dist(u, in.y)) + max(st.Dist(v, out.x), st.Dist(v, out.y));
Chkmin(ans, 2 * n - 4 - sum + w);
}
return ans;
}
int main() {
// freopen("tsp.in", "r", stdin);
// freopen("tsp.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w, u++, v++;
if(w == 1) {
g[u].push_back(v);
g[v].push_back(u);
} else {
edge[++e] = Edge {u, v, w};
}
}
DFS(1, 0);
st.Build();
sgt.Build();
DFS2(1, 0);
for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] + Diam(rev[i], rev[i], 0);
for(int i = n; i; i--) suf[i] = suf[i + 1] + Diam(rev[i], rev[i], 0);
int ans = 2 * n - 2 - pre[n].d;
for(int i = 1; i <= e; i++) {
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
if(w >= min(st.Dist(u, v), (n + 1) / 2)) {
edge[i].w = n + 1;
continue;
}
Chkmin(ans, 2 * n - 2 - Query(u, v) - st.Dist(u, v) + w);
}
int cen = Root(1, 0);
DFS3(cen, 0);
sort(g[cen].begin(), g[cen].end(),
[&](int i, int j) { return nsz[i] > nsz[j]; });
if(g[cen].size() > 0) Chkmin(ans, Solve(g[cen][0], cen));
if(g[cen].size() > 1) Chkmin(ans, Solve(g[cen][1], cen));
cout << ans << "\n";
return 0;
}
浙公网安备 33010602011771号