zoukankan      html  css  js  c++  java
  • HDU 3686 Traffic Real Time Query System(双连通分量缩点+LCA)(2010 Asia Hangzhou Regional Contest)

    Problem Description
    City C is really a nightmare of all drivers for its traffic jams. To solve the traffic problem, the mayor plans to build a RTQS (Real Time Query System) to monitor all traffic situations. City C is made up of N crossings and M roads, and each road connects two crossings. All roads are bidirectional. One of the important tasks of RTQS is to answer some queries about route-choice problem. Specifically, the task is to find the crossings which a driver MUST pass when he is driving from one given road to another given road.
     
    Input
    There are multiple test cases.
    For each test case:
    The first line contains two integers N and M, representing the number of the crossings and roads.
    The next M lines describe the roads. In those M lines, the ith line (i starts from 1)contains two integers Xi and Yi, representing that roadi connects crossing Xi and Yi (Xi≠Yi).
    The following line contains a single integer Q, representing the number of RTQs.
    Then Q lines follows, each describing a RTQ by two integers S and T(S≠T) meaning that a driver is now driving on the roads and he wants to reach roadt . It will be always at least one way from roads to roadt.
    The input ends with a line of “0 0”.
    Please note that: 0<N<=10000, 0<M<=100000, 0<Q<=10000, 0<Xi,Yi<=N, 0<S,T<=M 
     
    Output
    For each RTQ prints a line containing a single integer representing the number of crossings which the driver MUST pass.
     
    题目大意:给一个N个点M条边的无向图,然后有Q个询问X,Y,问第X条边到第Y条边必需要经过的点有多少个。
    思路:对于两条边X,Y,若他们在同一个双连通分量中,那他们之间至少有两条路径可以互相到达。
    那么,对于两条不在同一个分量中的边X,Y,显然从X到Y必须要经过的的点的数目等于从X所在的边双连通分量到Y所在的双连通分量中的割点的数目。
    于是,可以找出所有双连通分量,缩成一个点。对于 双连通分量A - 割点 - 双连通分量B,重构图成3个点 A - 割点 - B。
    那么,所有的双连通分量缩完之后,新图G‘成了一棵树(若存在环,那么环里的点都在同一个双连通分量中,矛盾)
    那么题目变成了:从X所在的G'中的点,到Y所在的G’中的点的路径中,有多少点是由割点变成的。
    由于图G‘中的点都是 双连通分量 - 割点 - 双连通分量 - 割点 - 双连通分量……的形式(两个双连通分量不会连在一起)
    那么X到Y的割点的数目就等于两点距离除以2,暨(dep[x] + dep[Y] - dep[LCA(X, Y)]) / 2,其中dep是深度,lca是最近公共祖先。
    其中LCA用tarjan也好,用RMQ也好,随意。我用的是离线的tarjan。
     
    细节:因为找边双连通分量的思路错了跪了一整天……写一下正确又优美的姿势是怎么样的>_<
    类似于tarjan求强联通的算法,用一个vis[]数组记录某条边是否被访问过。
    每次第一次访问一条边,把这条边压入栈中,在遍历完某条边指向的点之后,若pre[u] <= lowlink[v](见code),把栈中的边都设为同一个边双连通分量。
    仔细想想觉得应该是对的。我不会证明>_<
     
    PS:新图G’中点的数目可能会高达2*n的等级,试想一下原图恰好是一条链的情况,由于存在重边,边数也最好要2*m。
     
    代码(178MS):
      1 #include <cstdio>
      2 #include <iostream>
      3 #include <algorithm>
      4 #include <cstring>
      5 #include <vector>
      6 using namespace std;
      7 typedef long long LL;
      8 
      9 typedef pair<int, int> PII;
     10 
     11 const int MAXV = 10010;
     12 const int MAXE = 200010;
     13 
     14 int ans[MAXV];
     15 vector<PII> query[MAXV << 1];
     16 
     17 struct SccGraph {
     18     int head[MAXV << 1], fa[MAXV << 1], ecnt;
     19     bool vis[MAXV << 1];
     20     int to[MAXE << 1], next[MAXE << 1];
     21     int dep[MAXV << 1];
     22 
     23     void init(int n) {
     24         memset(head, -1, sizeof(int) * (n + 1));
     25         memset(vis, 0, sizeof(bool) * (n + 1));
     26         for(int i = 1; i <= n; ++i) fa[i] = i;
     27         ecnt = 0;
     28     }
     29 
     30     int find_set(int x) {
     31         return fa[x] == x ? x : fa[x] = find_set(fa[x]);
     32     }
     33 
     34     void add_edge(int u, int v) {
     35         to[ecnt] = v; next[ecnt] = head[u]; head[u] = ecnt++;
     36         to[ecnt] = u; next[ecnt] = head[v]; head[v] = ecnt++;
     37     }
     38 
     39     void lca(int u, int f, int deep) {
     40         dep[u] = deep;
     41         for(int p = head[u]; ~p; p = next[p]) {
     42             int &v = to[p];
     43             if(v == f || vis[v]) continue;
     44             lca(v, u, deep + 1);
     45             fa[v] = u;
     46         }
     47         vis[u] = true;
     48         for(vector<PII>::iterator it = query[u].begin(); it != query[u].end(); ++it) {
     49             if(vis[it->first]) {
     50                 ans[it->second] = (dep[u] + dep[it->first] - 2 * dep[find_set(it->first)]) / 2;
     51             }
     52         }
     53     }
     54 } G;
     55 
     56 int head[MAXV], lowlink[MAXV], pre[MAXV], ecnt, dfs_clock;
     57 int sccno[MAXV], scc_cnt;
     58 int to[MAXE], next[MAXE], scc_edge[MAXE];
     59 bool vis[MAXE], iscut[MAXV];
     60 int stk[MAXE], top;
     61 int n, m, q;
     62 
     63 void init() {
     64     memset(head, -1, sizeof(int) * (n + 1));
     65     memset(pre, 0, sizeof(int) * (n + 1));
     66     memset(iscut, 0, sizeof(bool) * (n + 1));
     67     memset(vis, 0, sizeof(bool) * (2 * m));
     68     ecnt = scc_cnt = dfs_clock = 0;
     69 }
     70 
     71 void add_edge(int u, int v) {
     72     to[ecnt] = v; next[ecnt] = head[u]; head[u] = ecnt++;
     73     to[ecnt] = u; next[ecnt] = head[v]; head[v] = ecnt++;
     74 }
     75 
     76 void tarjan(int u, int f) {
     77     pre[u] = lowlink[u] = ++dfs_clock;
     78     int child = 0;
     79     for(int p = head[u]; ~p; p = next[p]) {
     80         if(vis[p]) continue;
     81         vis[p] = vis[p ^ 1] = true;
     82         stk[++top] = p;
     83         int &v = to[p];
     84         if(!pre[v]) {
     85             ++child;
     86             tarjan(v, u);
     87             lowlink[u] = min(lowlink[u], lowlink[v]);
     88             if(pre[u] <= lowlink[v]) {
     89                 iscut[u] = true;
     90                 ++scc_cnt;
     91                 while(true) {
     92                     int t = stk[top--];
     93                     scc_edge[t] = scc_edge[t ^ 1] = scc_cnt;
     94                     if(t == p) break;
     95                 }
     96             }
     97         } else lowlink[u] = min(lowlink[u], pre[v]);
     98     }
     99     if(f < 1 && child == 1) iscut[u] = false;
    100 }
    101 
    102 void build() {
    103     G.init(scc_cnt);
    104     for(int p = 0; p != ecnt; ++p) {
    105         int &v = to[p];
    106         if(iscut[v]) G.add_edge(sccno[v], scc_edge[p]);
    107     }
    108 }
    109 
    110 void solve() {
    111     for(int i = 1; i <= n; ++i)
    112         if(!pre[i]) tarjan(i, 0);
    113     for(int u = 1; u <= n; ++u)
    114         if(iscut[u]) sccno[u] = ++scc_cnt;
    115 }
    116 
    117 int main() {
    118     while(scanf("%d%d", &n, &m) != EOF) {
    119         if(n == 0 && m == 0) break;
    120         init();
    121         for(int i = 1; i <= m; ++i) {
    122             int u, v;
    123             scanf("%d%d", &u, &v);
    124             add_edge(u, v);
    125         }
    126         solve();
    127         build();
    128         for(int i = 0; i <= scc_cnt; ++i) query[i].clear();
    129         scanf("%d", &q);
    130         for(int i = 0; i < q; ++i) {
    131             int x, y;
    132             scanf("%d%d", &x, &y);
    133             x = scc_edge[x * 2 - 2]; y = scc_edge[y * 2 - 2];
    134             query[x].push_back(make_pair(y, i));
    135             query[y].push_back(make_pair(x, i));
    136         }
    137         for(int i = 1; i <= scc_cnt; ++i) if(!G.vis[i]) G.lca(i, 0, 0);
    138         for(int i = 0; i < q; ++i) printf("%d
    ", ans[i]);
    139     }
    140 }
    View Code
  • 相关阅读:
    java小知识点8
    MongoDB执行计划分析详解(1)
    面对Schema free 的MongoDB,如何规范你的schema
    Mongodb简介
    编程之法:面试和算法心得(最大连续乘积子串)
    编程之法:面试和算法心得(荷兰国旗)
    编程之法:面试和算法心得(奇偶调序)
    编程之法:面试和算法心得(最大连续子数组和)
    编程之法:面试和算法心得(寻找和为定值的多个数)
    744. Find Smallest Letter Greater Than Target
  • 原文地址:https://www.cnblogs.com/oyking/p/3645984.html
Copyright © 2011-2022 走看看