The future breaking out of shades from the past
前置知识
Kruskal重构树,平衡树。(也就是标签了)。
Kruskal 重构树
不敢笑,这个东西赛时不会,导致死都优化不掉一只 \(\log\)。
考虑 Kruskal 的过程,我们的加边会造成两块的连通,而这两块如果想要连通,显然最小需要的边的大小也就是这条边的大小。
有没有什么办法能够便利的得到这个边呢?Kruskal 重构树就是在完成这个事情。具体的,每一次合并时,添加一个节点,这个节点的权值就是这个边的大小,然后让 \(u,v\) 所在的根节点都连到这个节点上。这个非常优秀啊,因为这样子我们就可以通过 \(LCA\) 找到这个边了。
解题思路
不妨先考虑从树入手,因为树的结构使得我们可以通过树剖找到任意两个点所引入的边的限制。然后不难想到的是前 \(k\) 个联通等价于前 \(k\) 个点相邻的联通,因此可以直接使用平衡树进行维护,每一次合并两个块的时候处理一下新的联通要求造成的限制就可以了。当然,这个的复杂度是错的,因为我们单次合并的复杂度可以来到 \(O(\log n)\) 级别,所以是 \(\log ^ 2 n\) 的。但是我们先不考虑这个,这个是后话了。
再考虑怎么把树上到图。这需要一定的思考。不妨先考虑如果只让你求几个点怎么求,肯定是跑 Kruskal 啊。这无不启示我们实际上图的有效信息是远比想象中少的。那既然反正是建生成树了,我们为什么不考虑对着整个图跑一次 Kruskal 呢?然后发现这个其实是不会丢信息的,证明显然。
因此图变成了树。那么我们该处理合并造成的 \(\log\) 了。考虑在 Kruskal 重构树中,任意两点的所需要的边也就是这两个点的 \(LCA\),因此我们可以尝试维护区间内的 \(LCA\)。但是维护 \(LCA\) 显然还是倒闭的,考虑转化成 \(LCA\) 的别样的信息,使得我们可以还原出 \(LCA\)。回忆一下树上的东西,几个点的 \(LCA\) 也就是 \(dfs\) 序最小的点和最大的点的 \(LCA\),这个是可以维护的。做完了!
Code
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define LL long long
#define fi first
#define se second
const int N = 5e5 + 10;
const int INF = 1e9;
struct edge{
int u,v,w;
}e[N];
int n,m,Q;
int fa[N << 1];
int a[N],val[N<<1],tot;
bool cmp(edge x,edge y){
return x.w < y.w;
}
int find(int x){
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
vector<int> G[N << 1];
int dfn[N << 1],idfn[N << 1];
namespace Tree_Cut{
int tot,fa[N << 1],son[N << 1],siz[N << 1],top[N << 1],dep[N << 1];
void dfs(int u,int rt){
fa[u] = rt;
siz[u] = 1;
dep[u] = dep[rt] + 1;
for(int v : G[u]){
dfs(v,u);
if(siz[v] > siz[son[u]]) son[u] = v;
siz[u] += siz[v];
}
}
void dfs2(int u,int rt,int tp){
top[u] = tp;
dfn[u] = ++tot;
idfn[tot] = u;
if(son[u])
dfs2(son[u],u,tp);
for(int v : G[u]){
if(v == son[u]) continue;
dfs2(v,u,v);
}
}
int LCA(int u,int v){
while(top[u] != top[v]){
if(dep[top[u]] > dep[top[v]]) swap(u,v);
v = top[v];
v = fa[v];
}
return (dep[u] < dep[v]) ? u : v;
}
};
int root = 0;
int cnt;
struct node{
int ls,rs;
pair<int,int> key;
int pri,siz;
int mn,mx,val;
};
struct Treap{
node tre[N << 1];
Treap(){
tre[0].mn = INF;
tre[0].mx = -INF;
}
void newnode(int id,pair<int,int> key){
cnt ++ ;
tre[cnt].key = key;
tre[cnt].ls = tre[cnt].rs = 0;
tre[cnt].pri = rand();
tre[cnt].siz = 1;
tre[cnt].mn = tre[cnt].mx = tre[cnt].val = dfn[id];
}
void pushup(int p){
tre[p].siz = tre[tre[p].ls].siz + tre[tre[p].rs].siz + 1;
tre[p].mx = max(tre[p].val,tre[tre[p].ls].mx);
tre[p].mx = max(tre[p].mx,tre[tre[p].rs].mx);
tre[p].mn = min(tre[p].val,tre[tre[p].ls].mn);
tre[p].mn = min(tre[p].mn,tre[tre[p].rs].mn);
}
void split(int u,pair<int,int> key,int &L,int &R){
if(u == 0){
L = R = 0;
return ;
}
if(tre[u].key <= key){
L = u;
split(tre[u].rs,key,tre[u].rs,R);
}
else{
R = u;
split(tre[u].ls,key,L,tre[u].ls);
}
pushup(u);
}
void split(int u,int k,int &L,int &R){
if(u == 0){
L = R = 0;
return ;
}
if(tre[tre[u].ls].siz + 1 <= k){
L = u;
split(tre[u].rs,k-tre[tre[u].ls].siz-1,tre[u].rs,R);
}
else{
R = u;
split(tre[u].ls,k,L,tre[u].ls);
}
pushup(u);
}
int merge(int L,int R){
if(L == 0 || R == 0) return L + R;
if(tre[L].pri > tre[R].pri){
tre[L].rs = merge(tre[L].rs,R);
pushup(L);
return L;
}
else{
tre[R].ls = merge(L,tre[R].ls);
pushup(R);
return R;
}
}
void del(pair<int,int> key){
int L,R,pos;
split(root,key,L,R);
pair<int,int> ekey = key;
ekey.se -- ;
split(L,ekey,L,pos);
root = merge(L,R);
}
void add(int i,pair<int,int> key){
newnode(i,key);
int L,R;
split(root,key,L,R);
root = merge(merge(L,cnt),R);
}
void change(int i,int val){
// find i, erase i, add i
pair<int,int> key = {-a[i],i};
del(key);
a[i] = val;
key = {-a[i],i};
add(i,key);
}
pair<int,int> query(int k){
int L,R;
split(root,k,L,R);
pair<int,int> res = {tre[L].mx,tre[L].mn};
root = merge(L,R);
return res;
}
}fhq;
int main(){
IOS;
// File("road");
srand(time(0));
cin >> n >> m >> Q;
for(int i=1;i<=n;i++)
cin >> a[i];
for(int i=1;i<=m;i++)
cin >> e[i].u >> e[i].v >> e[i].w;
sort(e+1,e+1+m,cmp);
for(int i=1;i<=n*2;i++) fa[i] = i;
for(int i=1;i<=m;i++){
int u = e[i].u,v = e[i].v,w = e[i].w;
if(find(u) == find(v)) continue;
u = find(u),v = find(v);
tot ++ ;
val[n+tot] = w;
G[n+tot].push_back(u);
G[n+tot].push_back(v);
fa[u] = n + tot;
fa[v] = n + tot;
}
Tree_Cut::dfs(n+tot,0);
Tree_Cut::dfs2(n+tot,0,n+tot);
for(int i=1;i<=n;i++)
fhq.add(i,{-a[i],i});
while(Q -- ){
int op;
cin >> op;
if(op == 1){
int c,t;
cin >> c >> t;
fhq.change(c,t);
}
else{
int k;
cin >> k;
if(k == 1){
cout << 0 << "\n";
continue;
}
pair<int,int> res = fhq.query(k);
int lca = Tree_Cut::LCA(idfn[res.fi],idfn[res.se]);
cout << val[lca] << "\n";
}
}
return 0;
}
总结
这题的关键转化有两个:一个是从图到树的转化,这个应该不难(我赛时想到了),另一个是使用 Kruskal 重构树维护信息(这个真的想不到)。虽然看到这种边的限制,确实应该是要想到 Kruskal 重构树的。

浙公网安备 33010602011771号