Timus Online Judge 2055. Urban Geography 分治,可回溯并查集,

2055. Urban Geography

Time limit: 2.0 second
Memory limit: 128 MB
Android Vasya prepares a project in Urban geography. The aim of the project is to improve the infrasructure of the city he lives in.
Now the city consists of  n districts, some of which are connected by roads. Using these roads one can get from any district to any other district of the city by car. Vasya thinks that such big amount of roads makes citizens use their own cars instead of walking or cycling. He wants to close as many roads for cars as possible and turn them into boulevards. Of course, Vasya wants to keep the possibility to get from any district to any other district of the city by car using roads.
Now citizens pay for using roads, and prices for different roads may vary very much. Vasya thinks that leaving open some expensive and some cheap roads at the same time is not a good idea beacuse it can increase social tension in the city. That’s why he wants to minimize the price spread between the most expensive and the cheapest roads. Help Vasya choose the roads to keep open.

Input

The first line contains integers  n и  m that are the number of city districts and roads correspondingly (2 ≤  n ≤ 50 000;  n − 1 ≤  m ≤ 50 000). The next  m lines contain triples of integers  a ib i and  c i, meaning that between the city districts  a i and  b i there is a road with the price  c i (1 ≤  a ib i ≤  na i ≠  b i; 1 ≤  c i ≤ 10 9). There can be several roads between two districts.

Output

In the only line output the sequence of integers — numbers of the roads which should be kept open in the city. The roads are numbered as they appear in the input data. If there are several solutions, output any of them.

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


你可能感兴趣的:(online,judge,urban,Timus,2055.,Geograph,可回溯并查集)