当前位置:编程学习 > C/C++ >>

Hdu 2419 Boring Game (数据结构_并查集)


题目大意:给定n个点m条边的无向图,每个点有点权。有q个操作,每个操作有三种:1、查询和某个点连通的大于k的最小点权 2、将某点的点权更新为k 3、删除某条边。

解题思路:08年网络赛的题目,全场5个人过,但绝对是难度中等的并查集好题。从这题我学到了对给定操作序列问题的一种很有效的处理方法--逆序处理,还学会STL里set的几个操作,收获颇丰啊。
     我们先不想其他的,考虑这个问题的三种操作要我们做什么。第一种操作就是找某个连通分量的大于k的最小点权,第二种和第三种如果是暴力q*n求解,那么就自然而然地模拟。
      但是复杂度太高了,q为30万,n为2万,最多操作60亿次。因为有牵扯到连通分量,考虑用并查集做。那么第一个操作就是要找某个根所有儿子中权值大于k的最小值,着好像和我们平时做的并查集不一样,平时都是利用根的信息,这就是本题的一个亮点。第二个操作似乎要先找到根再来遍历儿子,再来更新点权,第三个操作似乎要将某个集合拆成两个集合。
      这些操作如果正序来做,的确很吓人,第一个我们可以通过STL的set来应付,set的lower_bound方法不就是题目描述的这功能吗?第三个操作要拆集合很困难,但我们逆序转换成合并集合,则要简单、飘逸许多,也不影响结果。这就是本题的两个亮点。
     分析差不多就这样,接着就是代码实现,我第一次用set操作,参看了百度和某神牛的代码,效率还不错,256ms,排名第三。

测试数据:
3 3 8
4 5 8
1 2
2 3
1 3
F 1 4
E 1 3
F 2 7
E 2 3
E 1 2
F 2 7
U 3 6
F 3 3
out: Case XX: 4.500

1 0 1
0
F 1 0
out: Case XX: 0.00

3 3 8
8 8 8
1 2
2 3
1 3
F 1 9
F 1 8
E 1 3
E 2 3
E 1 2
F 1 8
U 3 6
F 3 7
out: Case XX: 4.000

3 3 9
8 8 8
1 2
2 3
1 3
F 1 9
F 1 8
U 1 5
E 2 3
E 1 2
E 1 3
F 1 5
U 1 6
F 1 6
out:Case XX: 4.750

3 3 11
8 8 8
1 2
2 3
1 3
F 1 9
F 1 8
U 1 5
F 1 6
E 2 3
E 1 2
E 1 3
U 1 4
F 1 5
U 1 6
F 1 6
out: Case XX: 4.400

C艹代码:
[cpp] 
#include <stdio.h> 
#include <iostream> 
#include <string.h> 
#include <set> 
#include <algorithm> 
using namespace std; 
#define MAX 20010 
#define PAIR pair<int,int> 
 
 
struct Query { 
 
    char ope[3]; 
    int  x,y; 
}query[300010]; 
double ans; 
int cost[MAX];                      //cost[i]表示每个点的权值 
int n,m,q,time,fa[MAX];             //fa[i]表示i属于哪个集合 
multiset<int> map[MAX];               //存放图,本题将无向图转换为有向图,因为只要判是否连通 
multiset<int> ver[MAX];               //存放以某点为父亲的所有节点 
 
 
int GetPa(int x) {                  //获取x点的父亲,并进行路径压缩 
 
    int p = x,tp; 
    while (p != fa[p]) p = fa[p]; 
    while (x != p) { 
 
        tp = fa[x]; 
        fa[x] = p,x = tp; 
    } 
    return p; 

void UnionSet(int x,int y) {        //将y属于的集合和x属于的集合合并 
 
    int px = GetPa(x); 
    int py = GetPa(y); 
    if (px == py) return; 
    if (px > py) swap(px,py); 
 
 
    fa[px] = py; 
    multiset<int>::iterator it; 
    for (it = ver[px].begin(); it != ver[px].end(); ++it) 
        ver[py].insert(*it);        //合并时子节点也需要合并 

 
 
int main() 

    int i,j,k,cas = 0,a,b,pa; 
 
 
    while (scanf("%d%d%d",&n,&m,&q) != EOF) { 
 
        for (i = 1; i <= n; ++i) { 
 
            fa[i] = i; 
            map[i].clear(); 
            ver[i].clear(); 
            scanf("%d",&cost[i]); 
        } 
        for (i = 1; i <= m; ++i) { 
 
            scanf("%d%d",&a,&b);     //边从小点连到大点 
            if (a < b) map[a].insert(b);  
            else map[b].insert(a); 
        } 
 
 
        for (i = 1; i <= q; ++i) {  //先处理所有操作,再往回查询,加边 
                                     
            scanf("%s %d%d",query[i].ope,&a,&b); 
            query[i].x = a,query[i].y = b; 
            if (query[i].ope[0] == 'U') { 
                //保存原来的点权,并更新点权 
                query[i].y = cost[a]; 
                cost[a] = b; 
            } 
            else if (query[i].ope[0] == 'E') { 
                //先删除这条边,后面再添加,这样不需要拆集合 
                if (a < b) map[a].erase(map[a].find(b)); 
                else map[b].erase(map[b].find(a)); 
            } 
        } 
 
 
        for (i = 1; i <= n; ++i) 
     &nb

补充:软件开发 , C++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,