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 }