征战蓝桥 —— 2015年第六届 —— C/C++A组第10题——灾后重建
題目
Pear市一共有N(<=50000)個居民點,居民點之間有M(<=200000)條雙向道路相連。這些居民點兩兩之間都可以通過雙向道路到達。
 這種情況一直持續到最近,一次嚴重的地震毀壞了全部M條道路。
 震后,Pear打算修復其中一些道路,修理第i條道路需要Pi的時間。不過,Pear并不打算讓全部的點連通,而是選擇一些標號特殊的點讓他們連通。
 Pear有Q(<=50000)次詢問,每次詢問,他會選擇所有編號在[tl,tr]之間,并且 編號 mod K = C 的點,修理一些路使得它們連通。
 由于所有道路的修理可以同時開工,所以完成修理的時間取決于花費時間最長的一條路,即涉及到的道路中Pi的最大值。
你能幫助Pear計算出每次詢問時需要花費的最少時間么?這里詢問是獨立的,也就是上一個詢問里的修理計劃并沒有付諸行動。
【輸入格式】
 第一行三個正整數N、M、Q,含義如題面所述。
 接下來M行,每行三個正整數Xi、Yi、Pi,表示一條連接Xi和Yi的雙向道路,修復需要Pi的時間。可能有自環,可能有重邊。1<=Pi<=1000000。
接下來Q行,每行四個正整數Li、Ri、Ki、Ci,表示這次詢問的點是[Li,Ri]區間中所有編號Mod Ki=Ci的點。保證參與詢問的點至少有兩個。
【輸出格式】
 輸出Q行,每行一個正整數表示對應詢問的答案。
【樣例輸入】
 7 10 4
 1 3 10
 2 6 9
 4 1 5
 3 7 4
 3 6 9
 1 5 8
 2 7 4
 3 2 10
 1 7 6
 7 6 9
 1 7 1 0
 1 7 3 1
 2 5 1 0
 3 7 2 1
【樣例輸出】
 9
 6
 8
 8
【數據范圍】
對于20%的數據,N,M,Q<=30
 對于40%的數據,N,M,Q<=2000
 對于100%的數據,N<=50000,M<=2*10^5,Q<=50000. Pi<=10^6. Li,Ri,Ki均在[1,N]范圍內,Ci在[0,對應詢問的Ki)范圍內。
資源約定:
 峰值內存消耗 < 256M
 CPU消耗 < 5000ms
請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入…” 的多余內容。
所有代碼放在同一個源文件中,調試通過后,拷貝提交該源碼。
注意: main函數需要返回0
 注意: 只使用ANSI C/ANSI C++ 標準,不要調用依賴于編譯環境或操作系統的特殊函數。
 注意: 所有依賴的函數必須明確地在源文件中 #include , 不能通過工程設置而省略常用頭文件。
提交時,注意選擇所期望的編譯器類型。
代碼
easy
#include <iostream> #include <set>using namespace std; int N, M, Q; const int MaxM = 2e5;//修正4:不能用10^5 const int MaxN = 50001;/*邊的抽象*/ struct Edge {int from, to, cost;//起點,終點,代價Edge(int from, int to, int cost) {this->from = from;this->to = to;this->cost = cost;} };bool cmp(Edge *e1, Edge *e2) {return e1->cost < e2->cost; }Edge *edges[MaxM];/*并查集*/ struct UFNode {UFNode *parent;UFNode() : parent(NULL) {} };UFNode *find(UFNode *p) {if (p->parent == NULL)return p;set<UFNode *> path;while (p->parent != NULL) {path.insert(p);p = p->parent;} //路徑壓縮,讓每個節點都能直接到達集團老大set<UFNode *>::iterator iter = path.begin();while (iter != path.end()) {(*iter)->parent = p;iter++;//修正1.指針后移}return p; }void merge(UFNode *p1, UFNode *p2) {find(p2)->parent = find(p1); }UFNode ufnodes[MaxN];//并查集的節點,一開始各自獨立void easy(int l, int r, int mod, int c) {for (int j = 0; j <=N ; ++j) {ufnodes[j].parent=NULL;//修正2:重新初始化}// 逐步加入邊到最小生成樹中for (int i = 0; i < M; ++i) {Edge *pEdge = edges[i];int from = pEdge->from;int to = pEdge->to;int cost = pEdge->cost;if (find(&ufnodes[from]) == find(&ufnodes[to]))//這兩個點已經在一棵樹上,這條邊不能采納continue;elsemerge(&ufnodes[from], &ufnodes[to]);// 如果這里求最小生成樹,if cnt==N-1 最小樹已經生成UFNode *parent = NULL;bool isOk=true;for (int i = l; i <= r; ++i) {if (i % mod == c)//i是關注點的編號{if (parent == NULL)parent = find(&ufnodes[i]);//第一個關注點的老大elseif(parent!=find(&ufnodes[i]))//沒有聯通{isOk=false;break;}}}if(isOk)//關注點都聯通了{printf("%d\n",cost);break;}} }int main(int argc, const char *argv[]) { // freopen("/Users/zhengwei/CLionProjects/lanqiao2018/2015_c_a/in/in5.txt", "r", stdin);scanf("%d %d %d", &N, &M, &Q);for (int i = 0; i < M; ++i) {int a, b, c;scanf("%d %d %d", &a, &b, &c);Edge *e = new Edge(a, b, c);edges[i] = e;} // 排序sort(edges, edges + M, cmp);//修正3.排序邊界for (int i = 0; i < Q; ++i) {int l, r, mod, c;scanf("%d %d %d %d", &l, &r, &mod, &c);easy(l, r, mod, c);}return 0; }mid
#include <iostream> #include <algorithm> #include <vector> #include <set>using namespace std; int N, M, Q; const int MaxM = 2e5;//修正4:不能用10^5 const int MaxN = 50001;/*邊的抽象*/ struct Edge {int from, to, cost;//起點,終點,代價Edge(int from, int to, int cost) {this->from = from;this->to = to;this->cost = cost;} };bool cmp(Edge *e1, Edge *e2) {return e1->cost < e2->cost; }Edge *edges[MaxM];/*并查集*/ struct UFNode {UFNode *parent;UFNode() : parent(NULL) {} };UFNode *find(UFNode *p) {if (p->parent == NULL)return p;set<UFNode *> path;while (p->parent != NULL) {path.insert(p);p = p->parent;}set<UFNode *>::iterator iter = path.begin();while (iter != path.end()) {(*iter)->parent = p;iter++;//修正1.指針后移}return p; }void merge(UFNode *p1, UFNode *p2) {find(p2)->parent = find(p1); }UFNode ufnodes[MaxN];//并查集的節點,一開始各自獨立/*最小樹的生成及表示*/ vector<Edge *> mst[MaxN];void buildMST() {int cnt = 0;//已加入邊的數量for (int i = 0; i < M; ++i) {Edge *pEdge = edges[i];int from = pEdge->from;int to = pEdge->to;int cost = pEdge->cost;if (find(&ufnodes[from]) == find(&ufnodes[to]))//這兩個點已經在一棵樹上,這條邊不能采納continue;else {merge(&ufnodes[from], &ufnodes[to]);cnt++; // 將邊加入到mst(鄰接表)mst[from].push_back(pEdge);Edge *other = new Edge(to, from, cost);mst[to].push_back(other);if (cnt == N - 1)//構建完成{break;}}} }int ff[MaxN][17];//ff[i][j]指的是標號為i的節點往根節點的方向移動2^i次達到的節點的標號 ff[i][j]=ff[ff[i][j-1]][j-1] int mm[MaxN][17];//mm[i][j]指的是標號為i的節點往根節點的方向移動2^i次過程中的最大權 int depth[MaxN];//記錄每個點在mst中的深度 int vis[MaxN];//記錄某個點是否被訪問過 /**** @param start 開始的點標號* @param parent 父節點標號* @param depth 這個點的深度*/ void dfs(int start, int parent, int d) {depth[start] = d + 1;vis[start] = 1; // 先向上走for (int i = 1; i < 17; ++i) {ff[start][i] = ff[ff[start][i - 1]][i - 1];mm[start][i] = max(mm[start][i - 1], mm[ff[start][i - 1]][i - 1]);} // 向下遞歸,找到所有兒子(所有鄰居)for (int i = 0; i < mst[start].size(); ++i) {Edge *child = mst[start][i];//兒子if (vis[child->to])continue;ff[child->to][0] = start;mm[child->to][0] = child->cost;dfs(child->to, start, d + 1);} }void preLca() {ff[1][0] = 1;//定義1號節點為根節點mm[1][0] = 0;//定義1號節點為根節點,它向上一步就沒了,dfs(1, 1, 0); } /*倍增法,求lca,順便求max權重*/ int maxUsingLca(int a, int b) {int ans = -1; // 1.將a深度調到更深(交換)if (depth[a] < depth[b]) {int t = a;a = b;b = t;} //2.將a調到和b同一高度int k = depth[a] - depth[b];//高度差for (int i = 0; (1 << i) <= k; ++i) {//k的二進制101if ((1 << i) & k)//k二進制的第i(從右往左)位是1{ans = max(ans, mm[a][i]);a = ff[a][i];}} // 至此,a和b已經在同一層上 //從最頂層開始遍歷,求ab兩點的lca的下一層if(a!=b) {//重要更新for (int j = 16; j >= 0; --j) {if (ff[a][j] == ff[b][j])continue;//從最大祖先開始,判斷a,b祖先,是否相同,// 一開始肯定相同,直到它們都跳j到最近祖先的下一層時,這個else觸發else {ans = max(ans, mm[a][j]);ans = max(ans, mm[b][j]);a = ff[a][j];b = ff[b][j]; // break;//重要更新}} // 至此,a,b離lca還差一步 //再往上走一步就得到了lcaans = max(ans, mm[a][0]);ans = max(ans, mm[b][0]);}return ans; } void mid(int l, int r, int mod, int c) {int ans = -1;int left = l - l % mod + c;if (left < l)left += mod; // 遍歷關注點,兩兩在mst中用倍增法求lca順便求max權重for (; left + mod <= r; left += mod) {ans = max(ans, maxUsingLca(left, left + mod));}printf("%d\n", ans); }int main(int argc, const char *argv[]) { // freopen("/Users/zhengwei/CLionProjects/lanqiao2018/2015_c_a/in/in5.txt", "r", stdin);scanf("%d %d %d", &N, &M, &Q);for (int i = 0; i < M; ++i) {int a, b, c;scanf("%d %d %d", &a, &b, &c);Edge *e = new Edge(a, b, c);edges[i] = e;} // 排序sort(edges, edges + M, cmp);//修正3.排序邊界buildMST();//生成最小樹preLca();//在最小樹為倍增法做預處理for (int i = 0; i < Q; ++i) {int l, r, mod, c;scanf("%d %d %d %d", &l, &r, &mod, &c);mid(l, r, mod, c);}return 0; }hard
#include <iostream> #include <vector> #include <set>using namespace std; int N, M, Q; const int MaxM = 2e5;//修正4:不能用10^5 const int MaxN = 50001;/*邊的抽象*/ struct Edge {int from, to, cost;//起點,終點,代價Edge(int from, int to, int cost) {this->from = from;this->to = to;this->cost = cost;} };bool cmp(Edge *e1, Edge *e2) {return e1->cost < e2->cost; }Edge *edges[MaxM];/*并查集*/ struct UFNode {UFNode *parent;UFNode() : parent(NULL) {} };UFNode *find(UFNode *p) {if (p->parent == NULL)return p;set<UFNode *> path;while (p->parent != NULL) {path.insert(p);p = p->parent;}set<UFNode *>::iterator iter = path.begin();while (iter != path.end()) {(*iter)->parent = p;iter++;//修正1.指針后移}return p; }void merge(UFNode *p1, UFNode *p2) {find(p2)->parent = find(p1); }UFNode ufnodes[MaxN];//并查集的節點,一開始各自獨立/*最小樹的生成及表示*/ vector<Edge *> mst[MaxN];void buildMST() {int cnt = 0;//已加入邊的數量for (int i = 0; i < M; ++i) {Edge *pEdge = edges[i];int from = pEdge->from;int to = pEdge->to;int cost = pEdge->cost;if (find(&ufnodes[from]) == find(&ufnodes[to]))//這兩個點已經在一棵樹上,這條邊不能采納continue;else {merge(&ufnodes[from], &ufnodes[to]);cnt++; // 將邊加入到mst(鄰接表)mst[from].push_back(pEdge);Edge *other = new Edge(to, from, cost);mst[to].push_back(other);if (cnt == N - 1)//構建完成{break;}}} }/*lca及最值查詢*/ int ff[MaxN][17];//ff[i][j]指的是標號為i的節點往根節點的方向移動2^i次達到的節點的標號 ff[i][j]=ff[ff[i][j-1]][j-1] int mm[MaxN][17];//mm[i][j]指的是標號為i的節點往根節點的方向移動2^i次過程中的最大權 int depth[MaxN];//記錄每個點在mst中的深度 int vis[MaxN];//記錄某個點是否被訪問過 /**** @param start 開始的點標號* @param parent 父節點標號* @param depth 這個點的深度*/ void dfs(int start, int parent, int d) {depth[start] = d + 1;vis[start] = 1; // 先向上走for (int i = 1; i < 17; ++i) {ff[start][i] = ff[ff[start][i - 1]][i - 1];mm[start][i] = max(mm[start][i - 1], mm[ff[start][i - 1]][i - 1]);} // 向下遞歸,找到所有兒子(所有鄰居)for (int i = 0; i < mst[start].size(); ++i) {Edge *child = mst[start][i];//兒子if (vis[child->to])continue;ff[child->to][0] = start;mm[child->to][0] = child->cost;dfs(child->to, start, d + 1);} }void preLca() {ff[1][0] = 1;//定義1號節點為根節點mm[1][0] = 0;//定義1號節點為根節點,它向上一步就沒了,dfs(1, 1, 0); }/*倍增法,求lca,順便求max權重*/ int maxUsingLca(int a, int b) {int ans = -1; // 1.將a深度調到更深(交換)if (depth[a] < depth[b]) {int t = a;a = b;b = t;} //2.將a調到和b同一高度int k = depth[a] - depth[b];//高度差for (int i = 0; (1 << i) <= k; ++i) {//k的二進制101if ((1 << i) & k)//k二進制的第i(從右往左)位是1{ans = max(ans, mm[a][i]);a = ff[a][i];}} // 至此,a和b已經在同一層上 //從最頂層開始遍歷,求ab兩點的lca的下一層if (a != b) {//=========此處為重要更新=========for (int j = 16; j >= 0; --j) {if (ff[a][j] == ff[b][j])continue;//從最大祖先開始,判斷a,b祖先,是否相同,// 一開始肯定相同,直到它們都跳j到最近祖先的下一層時,這個else觸發else {ans = max(ans, mm[a][j]);ans = max(ans, mm[b][j]);a = ff[a][j];b = ff[b][j]; // break;//重要更新,此處不能break}} // 至此,a,b離lca還差一步 //再往上走一步就得到了lcaans = max(ans, mm[a][0]);ans = max(ans, mm[b][0]);}return ans; }void mid(int l, int r, int mod, int c) {int ans = -1;int left = l - l % mod + c;if (left < l)left += mod; // 遍歷關注點,兩兩在mst中用倍增法求lca順便求max權重for (; left + mod <= r; left += mod) {int l = maxUsingLca(left, left + mod);ans = max(ans, l);}printf("%d\n", ans); }/*線段樹的定義,構建,及查詢*/ struct SegTree {int l, r, maxX;SegTree *lson, *rson; };int data[MaxN];//用來存儲線段樹的原始數據 SegTree *buildSegTree(int l, int r) {SegTree *stree = new SegTree();stree->l = l;stree->r = r;if (l == r) {stree->maxX = data[l];return stree;}int mid = (l + r) / 2;stree->lson = buildSegTree(l, mid);stree->rson = buildSegTree(mid + 1, r);stree->maxX = max(stree->lson->maxX, stree->rson->maxX);return stree; }int queryInSegTree(SegTree *root, int p1, int p2) {int l = root->l;int r = root->r;if (p1 <= l && p2 >= r)return root->maxX;//p1,p2完全覆蓋l~r的時候直接返回int mid = (l + r) / 2;int ans = -1;if (p1 <= mid)ans = max(ans, queryInSegTree(root->lson, p1, p2));if (p2 > mid)ans = max(ans, queryInSegTree(root->rson, p1, p2));return ans; }void hard(int l, int r, int mod, int c, SegTree *segTrees[]) {SegTree *tree = segTrees[mod * (mod - 1) / 2 + c + 1];int p1 = 0;if (l <= c)p1 = 1;elsep1 = (l - c) % mod == 0 ? (l - c) / mod + 1 : (l - c) / mod + 2;int p2 = (r - c) / mod;int ans = queryInSegTree(tree, p1, p2);printf("%d\n", ans); }int main(int argc, const char *argv[]) {freopen("/Users/zhengwei/CLionProjects/lanqiaobei2019/2015_A/data10/in8.txt", "r", stdin);freopen("/Users/zhengwei/CLionProjects/lanqiaobei2019/2015_A/data10/myout8.txt", "w", stdout);scanf("%d %d %d", &N, &M, &Q);for (int i = 0; i < M; ++i) {int a, b, c;scanf("%d %d %d", &a, &b, &c);Edge *e = new Edge(a, b, c);edges[i] = e;} // 排序sort(edges, edges + M, cmp);//修正3.排序邊界buildMST();//生成最小樹preLca();//在最小樹為倍增法做預處理int threshold = min(70, N / 3);/*生成很多的線段樹,具體來說,對小于等于70的每個mod,每個c都生成一顆線段樹*/SegTree *segTrees[threshold * (threshold + 1) / 2 + 1];int index = 1; // 對每個modfor (int _mod = 1; _mod <= threshold; ++_mod) { // 對每個余數 /* {//針對re=0,余數為0的情況int k = 1; // 迭代1~N中符合條件的關注點,兩兩連通求最大權重,存儲在data中for (; (k + 1) * _mod < N; k++) {data[k] = maxUsingLca(k * _mod, (k + 1) * _mod);}segTrees[index++] = buildSegTree(1, k);}*/for (int re = 0; re < _mod; ++re) { //具體來說1~N之間有多個關注點滿足%mod=c的情況,把這些點兩兩第計算出max權重,存儲在區間樹的原始數據中 //并依次來生成區間樹int k = 0; // 迭代1~N中符合條件的關注點,兩兩連通求最大權重,存儲在data中for (; (k + 1) * _mod + re <= N; k++) {data[k + 1] = maxUsingLca(k * _mod + re, (k + 1) * _mod + re);}segTrees[index++] = buildSegTree(1, k);}}for (int i = 0; i < Q; ++i) {int l, r, mod, c;scanf("%d %d %d %d", &l, &r, &mod, &c);if (mod > threshold)mid(l, r, mod, c);elsehard(l, r, mod, c, segTrees);}return 0; } 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的征战蓝桥 —— 2015年第六届 —— C/C++A组第10题——灾后重建的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 2015年第六届蓝桥杯 - 省赛 - C
- 下一篇: 第十届 蓝桥杯样题 —— 信用卡号验证
