hdu5441(并查集+离线处理)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5441

题意:

  根据输入的三个整数n、m、q,表示有n座城市,m条路,q次询问。下面给出m行,每行三个数start、end、w,分别是这条道路的起点城市、终点城市、“权值”。然后给出q次询问的数值,每次询问的结果是符合条件的路有多少条。条件就是:如果这条路的权值比给的限制值要小,那么这条路就是可行的。注意就是:对于一条路的添加来说,只要a到b之间有路,那么符合条件的路就会有两条,一条是a到b,还有一条是b到a。比如:添加了两条路,a到b、b到c,那么其实答案就是6条,(a,b)、(a,c)、(b,a)、(b,c)、(c,a)、(c,b)。

思路:

  刚开始用常规的并查集做法来写,发现会TLE,看了下数据,我之前那种遍历方法绝对会超时啊……看到q次询问就不加思索的对每次询问进行遍历,这样每次询问就要遍历一遍那m组数据,虽然实际可能不用遍历完m组,但是这个时间复杂度是很高的。然后网上学习了下,发现有一种方法很好,那就是不光对load[i].w进行排序,还可以对这q次询问的限制值进行排序这样的话只需要对m组数据遍历一次就够了。貌似这种方法有个名称叫做“离线处理”?(我个人理解应该是把查询的值先读入,读入后不在线处理,而是先存起来,排序之后再在遍历m组数据的时候使用)因为输出的时候是要按照输入时的顺序对应输出,所以这里需要有个表示查询值编号的数据。然后计算公式的话,两个集合合并,有增量有减量,合并后原来那两个集合不存在了,减量就是n1*(n1-1)、n2*(n2-1);合并后新出现的新集合存在一个增量:(n1+n2)*(n1+n2-1)。

代码:

  1 #include<iostream>
  2 #include<algorithm>
  3 using namespace std;
  4 
  5 const int maxn = 2e4 + 10;
  6 const int max_edge = 1e5 + 10;
  7 const int max_q = 5e3 + 10;
  8 
  9 int fa[maxn];
 10 int num[maxn];    //记录这个集合中点的个数
 11 
 12 struct Load
 13 {
 14     int start;
 15     int end;
 16     int w;
 17 } load[max_edge];
 18 
 19 struct Qnode
 20 {
 21     int sum;
 22     int id;        //记录编号,便于最后输出时是按编号输出
 23 } qnode[max_q];
 24 
 25 bool cmp1(Load a, Load b)        //按权值从小到大排序
 26 {
 27     return a.w < b.w;
 28 }
 29 
 30 bool cmp2(Qnode a, Qnode b)        //按限制值从小到大排序
 31 {
 32     return a.sum < b.sum;
 33 }
 34 
 35 void init(int n)
 36 {
 37     for(int i = 1; i <= n; i++)
 38     {
 39         fa[i] = i;
 40         num[i] = 1;
 41     }
 42     return;
 43 }
 44 
 45 int find(int x)
 46 {
 47     if(fa[x] == x)
 48     {
 49         return x;
 50     }
 51     else
 52     {
 53         return fa[x] = find(fa[x]);
 54     }
 55 }
 56 
 57 int main()
 58 {
 59     ios::sync_with_stdio(false);
 60     int t;
 61     int n, m, q;
 62 
 63     long long ans[max_q];
 64 
 65     cin >> t;
 66 
 67     while(t--)
 68     {
 69         cin >> n >> m >> q;
 70         init(n);
 71         for(int i = 1; i <= m; i++)
 72         {
 73             cin >> load[i].start >> load[i].end >> load[i].w;
 74             //发现这个交换是多余的……
 75             /*if(load[i].start > load[i].end)
 76             {
 77                 swap(load[i].start, load[i].end);
 78             }*/
 79         }
 80         sort(load + 1, load + 1 + m, cmp1);
 81 
 82         for(int i = 1; i <= q; i++)
 83         {
 84             cin >> qnode[i].sum;
 85             qnode[i].id = i;    //编号按顺序赋值
 86         }
 87         sort(qnode + 1, qnode + 1 + q, cmp2);
 88 
 89         int cnt = 1;
 90         long long tmp = 0;
 91         for(int i = 1; i <= m; i++)    //这里是直接遍历路径数据,效率比我之前想的高多了……
 92         {
 93             int x = find(load[i].start);
 94             int y = find(load[i].end);
 95 
 96             while(load[i].w > qnode[cnt].sum && cnt <= q)
 97             {
 98                 ans[qnode[cnt].id] = tmp;    //没有新的路添加,所以答案还是tmp,暂时不变
 99                 cnt++;    //当前这个小的qnode不能满足大于这条路的权值,那么就继续往下看比当前大的qnode是否符合条件
100             }
101             if(x != y)
102             {
103                 long long n1 = num[x], n2 = num[y];
104                 tmp += (n1 + n2) * (n1 + n2 - 1);
105                 tmp -= (n1 * (n1 - 1) + n2 * (n2 - 1));
106                 fa[x] = y;
107                 num[y] += num[x];    //x合并到y上,则x上点的个数也要加到y上
108             }
109         }
110         while(cnt <= q)    //这里的意思是,路径数据已经全部遍历完了,可能询问还没有结束,较小的询问已经处理过全部数据,那么较大的也一定能
111         {
112             ans[qnode[cnt++].id] = tmp;
113         }
114         for(int i = 1; i <= q; i++)
115         {
116             cout << ans[i] << endl;
117         }
118     }
119     return 0;
120 }

 

posted @ 2018-08-03 23:41  Piccolo_Devil  阅读(385)  评论(0编辑  收藏  举报