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 */