2055. Urban Geography
Memory limit: 128 MB
Input
Output
Samples
input | output |
---|---|
3 3 1 2 1 2 3 3 3 1 4 | 2 3 |
4 5 1 2 1 2 3 1 1 3 2 1 4 2 2 4 1 | 1 2 5 |
之前写的LCT,T了。学习了一下DSU的神奇用法,可回溯并查集。
http://codeforces.com/blog/entry/17579 这是cf上帖子,讲解的。
主要还是ztl给我讲解的。之前也看过某个标程的代码~~2015多校有道题也是dsu。
先讲讲可持久化并查集:
如果有路径压缩,那么不能实现可持久化,因为信息都被覆盖了。
那么只能用启发式的方式合并并查集:
1. 不进行路径压缩,定义Fa[u]表示u的父亲,R(u)表示u所在并查集的根,size[u],表示以u为根的并查集的点数
2. 对于加入u,v的边,那么查看R(u),和R(v)。如果
2.1 size[R(u)] > size[R(v)] 那么 令Fa[R(v)] = R(u) , size[R(u)] += size[R(v)]
2.2 否则交换u,v进行2.2操作
分析:由于以规模大的点为根,那么并查集的最深深度是log的,不用路径压缩也能在log的时间找到根
可以回退操作,如果进行了o1,o2,o3序列的并查集和合并操作,那么按o3,o2,o1的顺序回退
就能还原回原来并查集的状态。
使用这个性质,还能通过可持久化线段树,实现可持久化并查集的功能。
接下来说解法:
对于每条边设定加入时间和删除时间,以此实现分治,时间范围为0-m
1. 对边按从小到大排序
2. 二分最大边-最小边的值mid
2.1 对每个边记录时间范围,范围为开始时间=自己的位置,结束时间=边权<=当前边的边权+mid的边的最右边的位置
2.2 分治(L,R,low,high)表示处理L,R区间内的边(边的区间可能会调整,理解为处理R-L+1条边即可)
边的有效时间范围在low,high之间
2.3如果一条边有效时间覆盖low,high,那么把该边加入并查集中
2.4如果并查集的集合只有1个了,返回有解
2.5令 x = (low+high)/2 如果一条边时间点与low,x区间有重合进入到左边递归处理
2.6 如果左递归处理有解返回有解
2.7右递归如2.5,2.6一致
2.8将本次加入并查集的边删除
详细看代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
#define maxn 70005
int fa[maxn],size[maxn];
int find(int u){//并查集找根
if(fa[u] == u) return u;
return find(fa[u]);
}
int stack[maxn],ans[maxn],top;//栈记录加边是哪个子树合并到令一个树中
void set_union(int u,int v,int e){
u = find(u), v = find(v);
if(u == v) return ;
if(size[u] < size[v]) swap(u,v);
size[u] += size[v];//合并两个并查集,并且记录操作的内容,以及加入的边
fa[v] = u;
stack[top] = v;
ans[top++] = e;
}
void live_apart(int u){//分开两个并查集
int f = fa[u];
size[f] -= size[u];
fa[u] = u;
}
struct Edge{
int u,v,w,be,en,id;
};
Edge edge[maxn];
int comp(Edge a,Edge b){
return a.w < b.w;
}
int id1[maxn],id2[maxn];
int n,m;
int fenzhi(int L,int R,int low,int high){
if(L > R) return 0;
int ss = top;
int l,r,rp=R,u;
for(l=L;l<=rp;l++){//完全覆盖low,high时间范围的边直接加入并查集,
u = id1[l];
if(edge[u].be <= low && edge[u].en >= high){
swap(id1[l--],id1[rp--]);
set_union(edge[u].u,edge[u].v,u);
}
}
if(top == n-1) return 1;
int mid = (low+high)/2;//筛选出能进入左递归的边
for(r=rp,l=L;l<=r;l++)
if(edge[id1[l]].be > mid)
swap(id1[l--],id1[r--]);
if(fenzhi(L,l-1,low,mid)) return 1;
for(r=rp,l=L;l<=r;l++)//筛选出能进入右递归的边
if(edge[id1[l]].en <= mid)
swap(id1[l--],id1[r--]);
if(fenzhi(L,l-1,mid+1,high)) return 1;
while(top > ss)//回退并查集操作
live_apart(stack[--top]);
return 0;
}
int work(int mid){
for(int i = 0,j=0;i<m;i++){
while(j<m&&edge[j].w-edge[i].w <= mid)
j++;
edge[i].be = i;
edge[i].en = j-1;
id1[i] = i;
}
for(int i = 0;i <= n; i++)
size[i] = 1,fa[i] = i;
top = 0;
return fenzhi(0,m-1,0,m-1);
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 0;i < m; i++){
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
edge[i].id = i+1;
}
sort(edge,edge+m,comp);
int low = 0, high = edge[m-1].w - edge[0].w;
while(low <= high){
int mid = (high+low)/2;
if(work(mid))high = mid-1;
else low = mid+1;
}
work(low);
for(int i = 0;i < top ;i++){
if(i != 0) printf(" ");
printf("%d",edge[ans[i]].id);
}
printf("\n");
return 0;
}
/*
4 5
1 2 1
2 3 1
1 3 2
1 4 2
2 4 1
3 3
1 2 1
2 3 3
3 1 4
*/