色
給定一張圖,每個點有一個顏色。每次操作求改一個顏色,然後詢問所有不同顏色點對的最短距離。
給出一種 \(O(n\sqrt{n})\) 的做法。
先按照邊權從小到大排序,然後將邊分塊。
對於每一個塊,我們只需要快速判斷塊內是否存在一條邊的兩點顏色不同即可。
對於一個塊可以算出 \(\sum (col_{u_i}-col_{v_i})\times rnd_i\),其中 \(rnd_i\) 是隨機賦權的常數。當這個值不為 \(0\) 時就當前塊內就存在一條滿足條件的邊。
每次修改和查詢均為 \(O(\sqrt{n})\)。
[THUPC2022 初賽] 最小公倍樹
我們知道一條邊的權值為 \(\frac{uv}{\gcd(u,v)}\)。
考慮列舉兩個點的公因子 \(k\)。可以發現,對於一個 \(k\),在區間 \([L,R]\) 中的所有滿足條件的點 \(dk,(d+1)k,(d+2)k,\dots\),我們直接將它們向 \(dk\) 連邊是最優的。
所以總邊數是 \(O(n\log n)\) 的,然後跑一邊最小生成樹即可。
CF1120D Power Tree
我們將葉子節點按照 dfs 序排出來,那麼一個節點能夠控制的葉子節點必然是一個區間,記為\([l_x,r_x]\)。
控制一個點就相當於將一個區間增加。我們來考慮差分,相當於將 \(a_{l_x}\) 加,將 \(a_{r_x+1}\) 減。
所以我們能夠修改 \(a_1\) 到 \(a_{n+1}\) 的值。對於每一個區間,我們可以選擇連線一條邊 \(l_x\to r_x+1\),目標是讓所有點都與 \(n+1\) 連通。
跑一邊最小生成樹即可。注意要記錄所有可行的方案。
HNOI2010 城市建設
動態最小生成樹。用線段樹分治解決。
讓我們考慮不在操作區間 \([l,r]\) 中所有邊:
-
將區間 \([l,r]\) 中的邊設為 \(inf\),跑一邊最小生成樹後不在區間 \([l,r]\) 內且不在最小生成樹上的邊最後一定不會在最小生成樹上。
-
將區間 \([l,r]\) 中的邊設為 \(-inf\),跑一邊最小生成樹後不在區間 \([l,r]\) 內且在最小生成樹上的邊最後一定會在最小生成樹上。
最後分治到 \(l=r\) 時,只需要把剩下的邊再跑一邊最小生成樹就可以求出第 \(l\) 個詢問的答案。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e4 + 5, inf = 0x3f3f3f3f3f3f3f3f;
int n, m, q, fa[N], siz[N], flg[N], top;
int u[N], v[N], w[N], val[N], a[N], b[N];
vector<int> vec[N << 2];
int FindSet(int x) {return fa[x] == x ? x : FindSet(fa[x]);}
void insert(int p, int l, int r, int x, int y) {
int mid = l + r >> 1;
vec[p].push_back(y);
if(l == r) return;
if(x <= mid) insert(p << 1, l, mid, x, y);
else insert(p << 1 | 1, mid + 1, r, x, y);
}
struct node {int x, y;} stk[N];
void del(int p) {while(top > p) fa[stk[top].x] = stk[top].x, siz[stk[top].y] -= siz[stk[top].x], top--;}
void merge(int u, int v) {
if(siz[u] > siz[v]) swap(u, v);
stk[++top] = node({u, v}), fa[u] = v, siz[v] += siz[u];
}
void solve(int p, int l, int r, int ans, vector<int> g) {
int mid = l + r >> 1, now = top;
vector<int> vc;
if(l == r) {
val[a[l]] = w[a[l]] = b[l];
sort(g.begin(), g.end(), [](int i, int j) {return val[i] < val[j];});
for(auto x : g) {
int u = FindSet(::u[x]), v = FindSet(::v[x]);
if(u == v) continue;
merge(u, v); ans += val[x];
}
return printf("%lld\n", ans), del(now), void();
}
for(auto x : g) val[x] = w[x], flg[x] = 2; for(auto x : vec[p]) val[x] = inf;
sort(g.begin(), g.end(), [](int i, int j) {return val[i] < val[j];});
for(auto x : g) {
int u = FindSet(::u[x]), v = FindSet(::v[x]);
if(u == v) {if(val[x] != inf) flg[x] = 0; continue;}
merge(u, v);
} del(now);
for(auto x : vec[p]) val[x] = -inf;
sort(g.begin(), g.end(), [](int i, int j) {return val[i] < val[j];});
for(auto x : g) {
int u = FindSet(::u[x]), v = FindSet(::v[x]);
if(u == v) continue;
if(val[x] != -inf) flg[x] = 1; merge(u, v);
} del(now);
for(auto x : g) val[x] = w[x];
for(auto x : g) if(flg[x] == 1) {
int u = FindSet(::u[x]), v = FindSet(::v[x]);
if(u != v) merge(u, v), ans += w[x];
} else if(flg[x] == 2) vc.push_back(x);
solve(p << 1, l, mid, ans, vc), solve(p << 1 | 1, mid + 1, r, ans, vc), del(now);
}
signed main() {
scanf("%lld %lld %lld", &n, &m, &q);
for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;
for(int i = 1; i <= m; i++) scanf("%lld %lld %lld", &u[i], &v[i], &w[i]), val[i] = w[i];
for(int i = 1; i <= q; i++) scanf("%lld %lld", &a[i], &b[i]), insert(1, 1, q, i, a[i]);
vector<int> g; for(int i = 1; i <= m; i++) g.push_back(i);
solve(1, 1, q, 0, g);
return 0;
}
UOJ176 新年的繁榮
首先發現若存在兩個點它們的權值相同,將它們之間連上邊肯定是優的。
考慮剩下權值互不相同的點。容易發現權值很小,於是考慮列舉權值,記為 \(i\)。
每次需要找到 \(x\ and\ y=i\) 且 \(x,y\) 不在一個連通塊中的點,並將它們連上邊。
但這樣時間複雜度不對。可以考慮將 \(x\) 下放到 \(x\) 的子集中,就只需要列舉 \(i\ or\ 2^j\) 即可。
APIO2008 免費道路
sol1
將第 \(i\) 條邊附上權值 \(i\),然後跑一邊與 P2619 Tree I 相同的二分即可。
sol2
我們可以先找出所有必須被選擇的鵝卵石路,可以透過一次最小生成樹來解決。
然後將這些鵝卵石路強制選上,之後再優先選鵝卵石路直到選了 \(k\) 條,也可以透過一次最小生成樹來解決。
AGC037D Sorting a Grid
若能夠使 \(C\) 變成 \(D\),那麼 \(C\) 需要滿足 \(C\) 的每一行上的數與 \(D\) 的對應行上的數重排後相同。
若能夠使 \(B\) 變成 \(C\),那麼 \(B\) 需要滿足 \(B\) 的每一列上在 \(D\) 中的行(顏色)互不相同。
讓我們依次列舉每一列,我們可以將 \(A\) 的一行開一個點,將每種顏色開一個點,然後將行與上面有的顏色連一條邊,然後跑一邊二分圖匹配。
每次找到的一個完美匹配就可以確定 \(B\) 的一列。
容易從 \(B\) 變到 \(C\)。
BZOJ4808 馬
按照馬移動的方式連邊,然後跑一邊最大匹配來求最大獨立集。