例题一:区间最小生成树(NKOJ P8439)

简要题意:

一个n个点m条边的无向图,点编号1到n,边编号1到m。边有边权。
有q次操作,操作分两种:
1.k x y z:修改第k条边,使其连接的两点为x和y,边权为z;
2.x y:只用编号在区间[x,y]的边构成最小生成树的边权和是多少?无解输出”Impossible”
n<=200,m<=30000,q<=30000

分析:

这道题的边权是动态的,如果每次询问都重新求一遍kruskal,耗时O(qmlogm),TLE,妥妥滴!

但是注意到这道题的$n<=200$,这使得有用的边不会超过$200$。

再加上这道题询问,询问的是一段区间的边。

我们可以用线段树,来维护一段区间的边中,哪些边构成了最小生成树(或者最小生成树森林),即线段树的每个节点维护一个vector来记录形成最小生成树的边。

当线段树中,点$p$的左右儿子$ls,rs$要合并,可以用类似“归并排序”的方式,将两个vector合并成一个。

线段树维护各个vector,耗时O(nmlogm)

修改+得到答案耗时O(nqlogm)

最终我们耗时:O(nmlogm+nqlogm),easy~~

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define LL long long static char buf[1<<20], *p1=buf, *p2=buf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<20, stdin), p1==p2)?EOF:*p1++
inline int read()
{
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while('0'<=c&&c<='9')x=x*10+c-48,c=getchar();
return x;
}
const int N=30005; int n, m, q; int fa[N];int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);} struct node{int x, y, z;}rdin[N];
vector<node> G[N<<3]; vector<node> update(vector<node>A, vector<node>B)
{
vector<node>ret;
ret.clear();
int i=0, j=0, cnt=0;
int p=A.size(), q=B.size();
for(re o=1;o<=n;++o)fa[o]=o;
while(i<p&&j<q&&cnt<n-1)
{
if(A[i].z<=B[j].z)
{
int f1=getf(A[i].x), f2=getf(A[i].y);
if(f1!=f2)
{
cnt++;
fa[f1]=f2;
ret.push_back(A[i]);
}
i++;
}
else
{
int f1=getf(B[j].x), f2=getf(B[j].y);
if(f1!=f2)
{
cnt++;
fa[f1]=f2;
ret.push_back(B[j]);
}
j++;
}
}
while(i<p&&cnt<n-1)
{
int f1=getf(A[i].x), f2=getf(A[i].y);
if(f1!=f2)
{
cnt++;
fa[f1]=f2;
ret.push_back(A[i]);
}
i++;
}
while(j<q&&cnt<n-1)
{
int f1=getf(B[j].x), f2=getf(B[j].y);
if(f1!=f2)
{
cnt++;
fa[f1]=f2;
ret.push_back(B[j]);
}
j++;
}
return ret;
}
void modi(int p, int l, int r, int id, node w)
{
if(l == id && id == r)
{
G[p].clear();
G[p].push_back(w);
return;
}
int mid=(l+r)>>1, ls=(p<<1), rs=(p<<1|1);
if(id <= mid) modi(ls, l, mid, id, w);
else modi(rs, mid+1, r, id, w); G[p] = update(G[ls], G[rs]);
}
void build(int p, int l, int r)
{
if(l == r)
{
G[p].push_back(rdin[l]);
return;
}
int mid=(l+r)>>1, ls=(p<<1), rs=(p<<1|1);
build(ls, l, mid);
build(rs, mid+1, r);
G[p]=update(G[ls], G[rs]);
}
vector<node> query(int p, int l, int r, int x, int y)
{
if(x <= l && r <= y) return G[p];
int mid=(l+r)>>1, ls=(p<<1), rs=(p<<1|1);
if(x<=mid && y<=mid)return query(ls, l, mid, x, y);
if(x>mid && y>mid)return query(rs, mid+1, r, x, y);
vector<node>A, B;
A=query(ls, l, mid, x, y);
B=query(rs, mid+1, r, x, y);
return update(A, B);
} int work(vector<node>A)
{
for(re i=1;i<=n;++i)fa[i]=i;
int k=0, sz = A.size(), ret=0, cnt=0;
while(k<sz && cnt<n-1)
{
int f1=getf(A[k].x), f2=getf(A[k].y), v=A[k].z;
if(f1!=f2)
{
fa[f1]=f2;
ret+=v;
cnt++;
}
k++;
}
return cnt==n-1?ret:-1;
}
int main()
{
n=read();m=read();q=read();
for(re i=1;i<=m;++i)
{
int x=read(), y=read(), z=read();
rdin[i]=(node){x, y, z};
}
build(1, 1, m);
while(q--)
{
int t, k, x, y, z;
t=read();
if(t==1)
{
k=read();x=read();y=read();z=read();
modi(1, 1, m, k, (node){x, y, z});
}
else
{
x=read();y=read();
int Ans = work(query(1, 1, m, x, y));
if(Ans == -1) puts("Impossible");
else printf("%d\n", Ans);
}
}
return 0;
}

例题二:城市建设

简要题意:

n个点,m条边,边有初始的边权,Q次 修改+询问。

每次修改编号为$k$的边,将边权修改为$d$,修改完后,输出当前图的最小生成树边权总和。

n≤20000,m≤50000,Q≤50000。

分析:

这道题就很恐怖,如果我们暴力的修改完边后求kruskal,耗时O(Qmlogm),TLE。

但是不这么做,又能怎么办呢?毕竟点数和边数实在是太大了!!

问题来了,有没有什么办法能将边数减少呢?

我们来分析一下。

一条边,如果一直没有被修改,我们通过两种极限,来判断这条边是否需要。

1.假设所有修改后的边权,均为正无穷+INF,进行kruskal时这些修改的边优先级极地,相对的,其他边优先级较高,如果一条不修改的边,在这种情况下都用不上,在各个最小生成树中肯定不会出现,所以这条边我们可以直接删掉。(去掉一定不需要的边)

2.假设所有修改后的边权均为负无穷-INF,此时我们进行kruskal,不修改的边优先级相对低。这种情况下,不修改的边都需要选上,那么该边肯定为各个最小生成树中的必要边,为了减少边的讨论,我们可以将这条边合为一点,以减少边的条数。(缩点一定需要的边)

通过以上方法可以大幅度减少边的条数。

我们用类似CDQ的方法来进行分治,分治Q次修改。

进行$CDQ(l,r)$操作时,我们将$(l,r)$的修改的边进行上列操作,减少边的条数。

当$l==r$时,我们才进行真正的修改操作,修改完边权后kruskal。

(CDQ过程中,只是用来减少边数,真正的修改边权,是在$l==r$时进行)

值得注意的是,这个时候的边数已经相当的小了,而且$l$以前的修改我们已经处理(CDQ是先左区间,后右区间)。

这道题的思路就是这样,但是我们从$CDQ(l, r)$转移到$CDQ(l, mid),CDQ(mid+1,r)$时,需要保存大量信息:1.我们可能需要的边(去除一定不要的边);2.将一条边缩微点后的贡献。于是,按$CDQ$深度来保存信息,是相当优秀的方法。

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define LL long long
#define py pair<int,int>
#define fi first
#define se second static char buf[1<<20], *p1=buf, *p2=buf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<20, stdin), p1==p2)?EOF:*p1++
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while('0'<=c&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();
return x;
} const int N=5e4+5, INF=1e9; struct node{
int x, y, v, id;
bool operator<(const node&p)const{
return v < p.v;
}
};
node E[30][N], t[N], nw[N];
int rak[N], A[N];
LL Ans[N];
py ask[N]; int fa[N];int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);}
LL suodian(int &tot)
{// 合并一定需要的边
for(re i=1;i<=tot;++i) fa[t[i].x]=t[i].x, fa[t[i].y]=t[i].y;
int cnt=0; LL val=0;
sort(t+1, t+1+tot);
for(re i=1;i<=tot;++i)
{
int x=t[i].x, y=t[i].y;
if(getf(x)!=getf(y))
{
fa[getf(x)]=getf(y);
nw[++cnt]=t[i];
}
}
for(re i=1;i<=cnt;++i) fa[nw[i].x]=nw[i].x, fa[nw[i].y]=nw[i].y;
for(re i=1;i<=cnt;++i)
{
int x=nw[i].x, y=nw[i].y, v=nw[i].v;
if(v!=-INF)
{
fa[getf(x)]=getf(y);
val += v;
}
}
cnt=0;
for(re i=1;i<=tot;++i)
{
int x=t[i].x, y=t[i].y;
if(getf(x)!=getf(y))
{
nw[++cnt]=t[i];
nw[cnt].x=getf(x);
nw[cnt].y=getf(y);
rak[t[i].id]=cnt;
}
}
tot=cnt;
for(re i=1;i<=tot;++i)t[i]=nw[i];
return val;
}
void qubian(int &tot)
{ // 去除一定不需要的边
for(re i=1;i<=tot;++i) fa[t[i].x]=t[i].x, fa[t[i].y]=t[i].y;
int cnt=0;
sort(t+1, t+1+tot);
for(re i=1;i<=tot;++i)
{
int x=t[i].x, y=t[i].y, v=t[i].v;
if(getf(x)==getf(y))
{
if(v==INF)
nw[++cnt]=t[i];
}
else
{
fa[getf(x)]=getf(y);
nw[++cnt]=t[i];
}
}
tot=cnt;
for(re i=1;i<=tot;++i)t[i]=nw[i];
}
void CDQ(int l, int r, int d, int tot, LL val)
{// 左,右,深度,边的总数,合并成点的贡献
if(l == r) A[ask[l].fi] = ask[l].se; // 更新边权
for(re i=1;i<=tot;++i)
{
t[i] = E[d][i]; // 继承
t[i].v = A[t[i].id]; // 边权更新
rak[t[i].id] = i; // 原边在新数组中的编号
}
if(l == r)
{
for(re i=1;i<=tot;++i) fa[t[i].x] = t[i].x, fa[t[i].y] = t[i].y;
sort(t+1, t+1+tot);//kruskal
Ans[l] = val;
for(re i=1;i<=tot;++i)
{
int x=t[i].x, y=t[i].y, v=t[i].v;
if(getf(x) != getf(y))
{
fa[getf(x)] = getf(y);
Ans[l] += v;
}
}
return;
}
for(re i=l;i<=r;++i) t[rak[ask[i].fi]].v = -INF;
val += suodian(tot);
for(re i=l;i<=r;++i) t[rak[ask[i].fi]].v = INF;
qubian(tot); for(re i=1;i<=tot;++i) E[d+1][i] = t[i];
int mid = (l+r)>>1;
CDQ(l, mid, d+1, tot, val);
CDQ(mid+1, r, d+1, tot, val);
} int main()
{
int n=read(), m=read(), q=read();
for(re i=1;i<=n;++i)fa[i]=i;
for(re i=1;i<=m;++i)
{
int x=read(), y=read(), z=read();
A[i]=z; // 原边权记录一下
E[0][i]=(node){x, y, z, i}; // 左右端点,边权,在原数组的编号
}
for(re i=1;i<=q;++i)
{
int x=read(), y=read();
ask[i]=py(x, y);
}
CDQ(1, q, 0, m, 0); // 分治
for(re i=1;i<=q;++i)printf("%lld\n", Ans[i]);
return 0;
}

Kruskal重构树-进阶的更多相关文章

  1. [bzoj 3732] Network (Kruskal重构树)

    kruskal重构树 Description 给你N个点的无向图 (1 <= N <= 15,000),记为:1-N. 图中有M条边 (1 <= M <= 30,000) ,第 ...

  2. 【BZOJ 3732】 Network Kruskal重构树+倍增LCA

    Kruskal重构树裸题, Sunshine互测的A题就是Kruskal重构树,我通过互测了解到了这个神奇的东西... 理解起来应该没什么难度吧,但是我的Peaks连WA,,, 省选估计要滚粗了TwT ...

  3. 【BZOJ-3545&3551】Peaks&加强版 Kruskal重构树 + 主席树 + DFS序 + 倍增

    3545: [ONTAK2010]Peaks Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1202  Solved: 321[Submit][Sta ...

  4. BZOJ 3551: [ONTAK2010]Peaks加强版 [Kruskal重构树 dfs序 主席树]

    3551: [ONTAK2010]Peaks加强版 题意:带权图,多组询问与一个点通过边权\(\le lim\)的边连通的点中点权k大值,强制在线 PoPoQQQ大爷题解传送门 说一下感受: 容易发现 ...

  5. bzoj 3551 kruskal重构树dfs序上的主席树

    强制在线 kruskal重构树,每两点间的最大边权即为其lca的点权. 倍增找,dfs序对应区间搞主席树 #include<cstdio> #include<cstring> ...

  6. kruskal重构树学习笔记

    \(kruskal\) 重构树学习笔记 前言 \(8102IONCC\) 中考到了,本蒟蒻不会,所以学一下. 前置知识 \(kruskal​\) 求最小(大)生成树,树上求 \(lca​\). 算法详 ...

  7. Kruskal重构树入门

    这个知识点好像咕咕咕了好长了..趁还没退役赶紧补一下吧.. 讲的非常简略,十分抱歉.. 前置知识 Kruskal算法 一定的数据结构基础(如主席树) Kruskal重构树 直接bb好像不是很好讲,那就 ...

  8. UOJ#407. 【IOI2018】狼人 Kruskal,kruskal重构树,主席树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ407.html 题解 套路啊. 先按照两个节点顺序各搞一个kruskal重构树,然后问题转化成两棵krus ...

  9. LOJ.2865.[IOI2018]狼人(Kruskal重构树 主席树)

    LOJ 洛谷 这题不就是Peaks(加强版)或者归程么..这算是\(IOI2018\)撞上\(NOI2018\)的题了? \(Kruskal\)重构树(具体是所有点按从小到大/从大到小的顺序,依次加入 ...

随机推荐

  1. Java Web实现登录验证码(Servlet+jsp)

    1.生成验证码图片(Servlet) import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import j ...

  2. 字符串出现的topK问题

    /** * return topK string * @param strings string字符串一维数组 strings * @param k int整型 the k * @return str ...

  3. 在PHP中灵活使用foreach+list处理多维数组

    先抛出问题,有时候我们接收到的参数是多维数组,我们需要将他们转成普通的数组,比如: $arr = [ [1, 2, [3, 4]], [5, 6, [7, 8]], ]; 我们需要的结果是元素1变成1 ...

  4. PHP中命名空间是怎样的存在(一)?

    命名空间其实早在PHP5.3就已经出现了.不过大部分同学可能在各种框架的使用中才会接触到命名空间的内容,当然,现代化的开发也都离不开这些能够快速产出的框架.这次我们不从框架的角度,仅从简单的代码角度来 ...

  5. html阴影 box-shadow

    右下阴影 div { box-shadow: 10px 10px 5px #888888; }四周阴影 div { box-shadow: 0 0 5px #888888; } div {box-sh ...

  6. Jmeter系列(14)- Setup与tearDown线程组

    与普通线程组区别 #Setup线程组:在普通线程组执⾏前触发 #tearDown线程组:在普通线程组执⾏后触发 线程组属性配置详情完全⼀致 使⽤策略建议 #Setup 线程组 – 压测执⾏准备阶段,准 ...

  7. ❤️【Android精进之路-03】创建第一个Android应用程序竟然如此简单❤️

    您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文会重点介绍如何创建第一个Android应用,以及如何使用Android Studio进行调试 干货满满,建议收藏,需要用到时常看看.小伙伴们如 ...

  8. genymotion从本地拖拽apk到模拟器失败,报错“An error occured while deploying the file……”-解决方案

    前两篇已经讲过genymotion的安装了,但genymotion构建的安卓模拟器的界面比较简洁,什么软件都没.那么我们进行测试之前,先将需要测试的apk安装到模拟器中,一般来说,直接将apk文件从本 ...

  9. Loj#2880-「JOISC 2014 Day3」稻草人【CDQ分治,单调栈,二分】

    正题 题目链接:https://loj.ac/problem/2880 题目大意 给出平面上的\(n\)个点,然后求有多少个矩形满足 左下角和右上角各有一个点 矩形之间没有其他点 \(1\leq n\ ...

  10. Redis之品鉴之旅(三)

    3)Set,可以去重的.无序的集合.可以取交集.并集.zset(sorted set),有序的.去重的集合,排序不是根据value排序,而是根据score排序. using (RedisClient ...