色
给定一张图,每个点有一个颜色。每次操作求改一个颜色,然后询问所有不同颜色点对的最短距离。
给出一种 \(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 马
按照马移动的方式连边,然后跑一边最大匹配来求最大独立集。
标签:10,val,int,auto,top,最小,2024,FindSet From: https://www.cnblogs.com/ddxrS/p/18453289