【CF687D】Dividing Kingdom II

题意:给你一张n个点m条边的无向图,边有边权$w_i$。有q个询问,每次给出l r,问你:如果只保留编号在[l,r]中的边,你需要将所有点分成两个集合,使得这个划分的代价最小,问最小代价是什么。一个划分的代价是指,对于所有两端点在同一集合中的边,这些边的边权最大值。如果没有端点在同一集合中的边,则输出-1。

$n,q\le 1000,m\le \frac {n(n-1)} 2,w_i\le 10^9$

题解:先考虑暴力的做法,我们将所有边按权值从大到小排序,然后一个一个加到带权并查集里,标记两端点不在同一集合中,如果一条边的两端点已经在同一集合中,则输出答案。

但是问题在于边数非常大,不过仔细分析发现,我们可以将所有边按加入并查集时的情况分成如下三种:

1.如果a和b不在同一连通块内,我们连接这两个连通块,并标记a和b不在同一集合中。

2.如果a和b在同一连通块内,且a和b不在同一集合,则我们不用管。

3.如果a和b在同一连通块内,且a和b在同一集合,则输出答案。

我们令1和3这样的边为关键边。容易发现下面两条重要的引理:

引理1:关键边的数目不超过n条。

引理2:如果我们忽视非关键边,答案不变。

证明是显然的。但是这给我们一个非常重要的思路:如果我们预处理出区间内所有的关键边,则我们可以把每次查询的复杂度由O(m)变成O(n)!

进一步的,我们可以用以边的编号为下标的线段树来维护并查集。对于每个结点,我们已经处理完了它的左右两个子节点,其中每个节点都维护了该区间内的不超过n条关键边,我们只需要将左右两个节点的关键边归并起来,再用并查集处理一下即可。然后查询时,我们把所有线段树上的区间的一共$O(n\log n)$条关键边拿出来,一起处理一下即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int maxn=1010;
const int maxm=500010;
typedef vector<int> vi;
int n,m,q;
vi s[maxm<<2];
vector<int>::iterator ia,ib;
int pa[maxm],pb[maxm],pc[maxm],f[maxn],g[maxn],p[maxm];
inline int rd()
{
int ret=0,f=1; char gc=getchar();
while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();}
while(gc>='0'&&gc<='9') ret=ret*10+(gc^'0'),gc=getchar();
return ret*f;
}
int find(int x)
{
if(f[x]==x) return x;
int t=f[x];
f[x]=find(t);
g[x]^=g[t];
return f[x];
}
inline vi merge(vi a,vi b)
{
int i,cnt=0,x,y;
vi c;
for(ia=a.begin();ia!=a.end();ia++) f[pa[*ia]]=pa[*ia],f[pb[*ia]]=pb[*ia],g[pa[*ia]]=g[pb[*ia]]=0;
for(ib=b.begin();ib!=b.end();ib++) f[pa[*ib]]=pa[*ib],f[pb[*ib]]=pb[*ib],g[pa[*ib]]=g[pb[*ib]]=0;
for(ia=a.begin(),ib=b.begin();ia!=a.end()||ib!=b.end();)
{
if(ia!=a.end()&&(ib==b.end()||pc[*ia]>pc[*ib])) p[++cnt]=*ia,ia++;
else p[++cnt]=*ib,ib++;
}
for(i=1;i<=cnt;i++)
{
x=pa[p[i]],y=pb[p[i]];
if(find(x)!=find(y)) g[f[x]]=g[x]^g[y]^1,f[f[x]]=f[y],c.push_back(p[i]);
else if(g[x]!=g[y]) continue;
else
{
c.push_back(p[i]);
break;
}
}
return c;
}
void build(int l,int r,int x)
{
if(l==r)
{
s[x].push_back(l);
return ;
}
int mid=(l+r)>>1;
build(l,mid,lson),build(mid+1,r,rson);
s[x]=merge(s[lson],s[rson]);
}
vi query(int l,int r,int x,int a,int b)
{
if(a<=l&&r<=b) return s[x];
int mid=(l+r)>>1;
if(b<=mid) return query(l,mid,lson,a,b);
if(a>mid) return query(mid+1,r,rson,a,b);
return merge(query(l,mid,lson,a,b),query(mid+1,r,rson,a,b));
}
int main()
{
//freopen("cf687D.in","r",stdin);
n=rd(),m=rd(),q=rd();
int i,a,b,x,y;
vi t;
for(i=1;i<=m;i++) pa[i]=rd(),pb[i]=rd(),pc[i]=rd();
build(1,m,1);
for(i=1;i<=q;i++)
{
a=rd(),b=rd();
t=query(1,m,1,a,b);
for(ia=t.begin();ia!=t.end();ia++) f[pa[*ia]]=pa[*ia],f[pb[*ia]]=pb[*ia];
for(ia=t.begin();ia!=t.end();ia++)
{
x=pa[*ia],y=pb[*ia];
if(find(x)==find(y)) break;
f[f[x]]=f[y];
}
if(ia==t.end()) puts("-1");
else printf("%d\n",pc[*ia]);
}
return 0;
}//5 9 1 4 1 46 1 3 29 3 2 58 1 5 61 2 4 88 1 2 87 4 5 58 3 5 69 3 4 28 2 7

【CF687D】Dividing Kingdom II 线段树+并查集的更多相关文章

  1. codeforces 687D Dividing Kingdom II 带权并查集(dsu)

    题意:给你m条边,每条边有一个权值,每次询问只保留编号l到r的边,让你把这个图分成两部分 一个方案的耗费是当前符合条件的边的最大权值(符合条件的边指两段点都在一个部分),问你如何分,可以让耗费最小 分 ...

  2. [WC2005]双面棋盘(线段树+并查集)

    线段树+并查集维护连通性. 好像 \(700ms\) 的时限把我的常数超级大的做法卡掉了, 必须要开 \(O_2\) 才行. 对于线段树的每一个结点都开左边的并查集,右边的并查集,然后合并. \(Co ...

  3. 2022.02.27 CF811E Vladik and Entertaining Flags(线段树+并查集)

    2022.02.27 CF811E Vladik and Entertaining Flags(线段树+并查集) https://www.luogu.com.cn/problem/CF811E Ste ...

  4. UVa 1455 Kingdom 线段树 并查集

    题意: 平面上有\(n\)个点,有一种操作和一种查询: \(road \, A \, B\):在\(a\),\(b\)两点之间加一条边 \(line C\):询问直线\(y=C\)经过的连通分量的个数 ...

  5. CF687D Dividing Kingdom II

    \(\mathtt{CF 687D}\) \(\mathcal{Description}\) 给你一个图有 \(n\) 个点 \((1 \leq n \leq 10^3)\) 和 \(m\) 条边 \ ...

  6. 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status ...

  7. 【XSY2707】snow 线段树 并查集

    题目描述 有\(n\)个人和一条长度为\(t\)的线段,每个人还有一个工作范围(是一个区间).最开始整条线段都是白的.定义每个人的工作长度是这个人的工作范围中白色部分的长度(会随着线段改变而改变).每 ...

  8. bzoj 2054: 疯狂的馒头(线段树||并查集)

    链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2054 线段树写法: 点的颜色只取决于最后一次染的颜色,所以我们可以倒着维护,如果当前区间之前 ...

  9. 【BZOJ1453】[Wc]Dface双面棋盘 线段树+并查集

    [BZOJ1453][Wc]Dface双面棋盘 Description Input Output Sample Input Sample Output HINT 题解:话说看到题的第一反应其实是LCT ...

随机推荐

  1. Thinkphp部署

    环境nginx,apache,iis naginx环境需要: apache需要: iis需要: 项目入口 1.根目录("/") 2.多级目目录(“/public”) 静态文件 静态 ...

  2. https://www.cnblogs.com/zy-jiayou/p/7661415.html

    Maven常用命令 1.1.Maven 参数 -D 传入属性参数 -P 使用pom中指定的配置 -e 显示maven运行出错的信息 -o 离线执行命令,即不去远程仓库更新包 -X 显示maven允许的 ...

  3. 11gR2 RAC:更换OCR、votedisk

    要点: ocrconfig 备份-恢复 ocrconfig 导出-导入 crsctl querry css votedisk crsctl replace votedisk {+dsikgroup|s ...

  4. Nginx 向客户端输出真实的后端IP地址

    因为涉及到内外网的改造,所以狠多东西现在需要依赖于openresty来做总控实现.然后就碰见了一个比较难办的问题,即在upstream时候,如何获取实际处理请求的server地址.假设有如下upstr ...

  5. java获取map中的最小KEY,最小VALUE

    import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map ...

  6. joda time, jackson 与 scala 反射

    1. scala 反射,获得所有 field name 可以直接从 case class 获得 field 而不必创建实例 (get fields of a class without an inst ...

  7. C#(少用的)

    挖一挖C#中那些我们不常用的东西之系列(1)——ToDictionary,ToLookup 挖一挖C#中那些我们不常用的东西之系列(2)——IsXXX 系列方法 挖一挖C#中那些我们不常用的东西之系列 ...

  8. PHP压缩html网页代码原理(清除空格,换行符,制表符,注释标记)

    本博启用了一个叫wp super cache的页面压缩工具, 源代码没有去查看,不过原理很简单. 我们可以自己动手书写一个压缩脚本. 清除换行符,清除制表符,去掉注释标记 .它所起到的作用不可小视. ...

  9. ios开发周期之--(向上,向下,四舍五入)取整

    ceil(x)返回不小于x的最小整数值(然后转换为double型). floor(x)返回不大于x的最大整数值. round(x)返回x的四舍五入整数值.

  10. android studio 使用 SVN

    通过android studio来进行版本控制,先前已经安装了TortoiseSVN-1.9.2,但是在打开android studio的时候会出现 Can't use Subversion comm ...