HDU 4297 One and One Story [有向带环图LCA]

  给一个有向图,每个顶点的出度为1,A和B是图中的两个顶点,A和B每次只能走一步或者不走,设他们相遇时A走了X步,B走了Y步,使max(x,y)最大,有多解时,使min(x,y)最小,如果还有多解,使x>=y。

  其实这个图最特殊的地方就是它每个顶点的出度仅为1。我们都知道树的特性是除了根节点外每个顶点的入度为1,那我们把这个图全部建反向边,也就是说每个顶点的入度为1。但是这个图是有环的,它没有入度为0的根,如果我们把环缩成一个点的话,就会发现这个环的整体入度为0,因为每个点在构环的时候入度就已经饱和了,同样,我们也很容易证明每个连通块至多只有一个环,因为一个连通块不可能有两个入度为0的点,这样建反向边并且缩环成点后,原图就变成了一棵树,原题就成了树上的LCA问题。

  写起来还是比较麻烦的,要预处理出每个环的长度,一个环上两点的相对距离,以及每个非环点进入环的第一个点。同时,为了方便,建一个虚拟节点0,向每个缩环的点引一条边。对于每次查询,如果LCA是虚拟节点,显然不连通,如果LCA是普通树节点,也可直接求解,最麻烦的情况就是进入了一个环,这时候肯定会让其中的一个人走,另一个人停下等待,因为他们两之间是一个追及的关系,如果都走那就永远遇不到了。。。讨论一下谁走满足题目要求即可。。

  1 #pragma comment(linker, "/STACK:1024000000,1024000000")
  2 #include <stdio.h>
  3 #include <string.h>
  4 #include <math.h>
  5 #include <algorithm>
  6 #define MAXN 500005
  7 using namespace std;
  8 
  9 struct edge{
 10     int v,e,n;
 11 }e[MAXN];
 12 int n, q, tu, tv, fa[MAXN], next[MAXN], p[MAXN];
 13 int first[MAXN], es;
 14 void addedge(int u,int v) {
 15     e[es].v = v, e[es].n = first[u], first[u] = es++;
 16 }
 17 //LCA+RMQ
 18 struct lcast{int dep, id;}list[MAXN*2], r[30][MAXN];
 19 int mm[MAXN*2], pos[MAXN], dis[MAXN], rid;
 20 void dfs(int u, int dep) {
 21     pos[u] = ++rid, list[rid].id = u, list[rid].dep = dep;
 22     dis[u] = dep;
 23     for (int i = first[u]; i != -1; i = e[i].n) {
 24         dfs(e[i].v, dep + 1);
 25         ++rid, list[rid].id = u, list[rid].dep = dep;
 26     }
 27 }
 28 void makermq() {
 29     rid = 0;
 30     dfs(0, 0);
 31     mm[1] = 0, r[0][1] = list[1];
 32     for(int i = 2; i <= rid; i ++) {
 33         mm[i] = (i&i-1) ? mm[i-1] : mm[i-1]+1;
 34         r[0][i] = list[i];
 35     }
 36     for (int i = 0; i < mm[rid]; i ++)
 37         for (int j = 1; j+(1<<i+1)-1 <= rid; j ++)
 38             r[i+1][j] = r[i][j].dep < r[i][j+(1<<i)].dep ? r[i][j] : r[i][j+(1<<i)];
 39 }
 40 int lca(int x, int y) {
 41     if(pos[x] > pos[y]) return lca(y, x);
 42     x = pos[x], y = pos[y];
 43     int k = mm[y - x + 1];
 44     y -= (1 << k) - 1;
 45     return r[k][x].dep < r[k][y].dep ? r[k][x].id : r[k][y].id;
 46 }
 47 //缩点构图
 48 int id[MAXN], cid[MAXN], cids, ids;
 49 //cirn 该环上点的个数,cirdis,环上两点的相对距离,cirin,进入环中的点
 50 int cirn[MAXN], cirdis[MAXN], cirin[MAXN];
 51 int dfsin(int u) {
 52     return cirin[u] ? cirin[u] : cirin[u] = dfsin(fa[u]);
 53 }
 54 void makegraph() {
 55     memset(cirn, 0 ,sizeof cirn);
 56     memset(cirin, 0, sizeof cirin);
 57     memset(id, 0, sizeof id); ids = cids = 0;
 58     for (int i = 1, j; i <= n; i++) {
 59         if (id[i]) continue;
 60         int sid = ids;
 61         for (j = i; !id[j]; j = fa[j]) id[j] = ++ids;
 62         //如果遇到的节点标记是这次一路找过来的,说明生成了一个环
 63         if (id[j] > sid || fa[i] == i) {
 64             //将该环中的点ID统一,并统计环中点的个数,以及环上两点的相对距离
 65             int cd = 1;
 66             ids = id[j], cid[++cids] = id[j], cirn[ids] = 1, cirdis[j] = cd, cirin[j] = j;
 67             for (int k = fa[j]; k != j; k = fa[k])
 68                 id[k] = ids, cirn[ids]++, cirdis[k] = ++cd, cirin[k] = k;
 69         }
 70     }
 71     //处理非环点进入环中的第一个点
 72     for (int i = 1; i <= n; i++) {
 73         if (cirin[i]) continue;
 74         else cirin[i] = dfsin(i);
 75     }
 76     memset(first, -1, sizeof first); es=0;
 77     for (int i = 1; i <= n; i++)
 78         if(id[i] != id[fa[i]]) addedge(id[fa[i]], id[i]);
 79     for (int i = 1; i <= cids; i++)
 80         addedge(0, cid[i]);
 81     makermq();
 82 }
 83 int main() {
 84     //freopen("test.in", "r", stdin);
 85     while (scanf("%d%d", &n, &q) != EOF) {
 86         for (int i = 1; i <= n; i++) p[i] = i;
 87         for (int i = 1; i <= n; i++)
 88             scanf("%d", &tu), fa[i] = tu;
 89         makegraph();
 90         while (q-- ) {
 91             scanf("%d%d", &tu, &tv);
 92             int lc = lca (id[tu],id[tv]);
 93             //LCA是虚拟根节点,说明不在一颗子树上
 94             if(lc == 0) printf("-1 -1\n");
 95             //LCA不是环,一般的LCA问题
 96             else if (cirn[lc] == 0){
 97                 printf("%d %d\n", dis[id[tu]] - dis[lc], dis[id[tv]] - dis[lc]);
 98             } else {
 99                 //uin和vin是u和v进入环的顶点,disu和disv是到环距离,det1和det2是u和v在环上要走的距离
100                 int uin = cirin[tu], vin = cirin[tv];
101                 int disu = dis[id[tu]] - dis[lc], disv = dis[id[tv]] - dis[lc];
102                 int det1 = (cirdis[vin] - cirdis[uin] + cirn[id[uin]]) % cirn[id[uin]];
103                 int det2 = (cirn[id[uin]] - cirdis[vin] + cirdis[uin]) % cirn[id[uin]];
104                 //保证解满足题意,最大值最小,然后是最小值最大,然后是A>=B
105                 int ansu = disu + det1, ansv = disv;
106                 if (max(disu+det1, disv) > max(disu, disv+det2))ansu = disu, ansv = disv+det2;
107                 else if(max(disu+det1, disv) < max(disu, disv+det2))ansu = disu+det1, ansv = disv;
108                 else if(min(disu+det1, disv) < min(disu, disv+det2))ansu = disu+det1, ansv = disv;
109                 else if(min(disu+det1, disv) > min(disu, disv+det2))ansu = disu, ansv = disv+det2;
110                 printf("%d %d\n", ansu, ansv);
111             }
112         }
113     }
114     return 0;
115 }

 

转载于:https://www.cnblogs.com/swm8023/archive/2012/09/20/2694777.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值