http://acm.hdu.edu.cn/showproblem.php?pid=4297
题意:
给出n个点的有向图,每个点的出度均为1.有m个询问,每个询问两个数(u,v),表示两个人一个在u一个在v。
对于每个询问,请你选择一个点P使得u、v均能到达P。设u到达P需要A步,v到达P需要B步。求一个P使得max(A,B)最小?
若答案不唯一,输出min(A,B)最小的;若答案还不唯一,输出A>=B的。(不存在P的A=B=-1)
思路:
这个有向图很特别,由于每个节点只有一条出边,所以如果形成一个环的话就只能有指向这个环的边,同时一个子图内最多存在一个环,tarjan搞掉环,
建反图虚拟根节点转换成一棵树,根节点如果是环的环用带关系的并查集维护,然后lca->rmq,在线得到答案。
话说这题太恶心了,高了好久才出来.......
#pragma comment (linker , "/STACK:1024000000,1024000000") #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define MIN(a , b) ((a) < (b) ? (a) : (b)) #define MAX(a , b) ((a) > (b) ? (a) : (b)) #define ll __int64 #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 100007 #define N 500007 using namespace std; //freopen("din.txt","r",stdin); struct node{ int v; int next; }edge[N<<1]; int head[N],ct; int dfn[N],low[N],stack[N],belong[N],cnt[N]; int top,idx,tot; bool isinS[N],vt[N]; int indeg[N],f[N],dis[N],up[N]; int E[N << 1],D[N << 1],R[N],dep[N],dp[N << 1][20]; int out[N],pow2[32]; int n,m; void add(int u,int v){ edge[ct].v = v; edge[ct].next = head[u]; head[u] = ct++; } void tarjan(int u){ dfn[u] = low[u] = ++idx; isinS[u] = true; stack[top++] = u; for (int i = head[u]; i != -1; i = edge[i].next){ int v = edge[i].v; if (dfn[v] == -1){ tarjan(v); low[u] = min(low[u],low[v]); } else if (isinS[v]){ low[u] = min(low[u],dfn[v]); } } int v; if (dfn[u] == low[u]){ do{ v = stack[--top]; isinS[v] = false; belong[v] = tot; cnt[tot]++; }while (u != v); tot++; } } int find(int x){ if (x == f[x]) return f[x]; int tmp = f[x]; f[x] = find(tmp); dis[x] += dis[tmp]; return f[x]; } void makeEage(){ int i; CL(head,-1); ct = 0; for (i = 1; i <= n; ++i){ dfn[i] = low[i] = -1; isinS[i] = false; cnt[i] = 0; } idx = 0; tot = 1; //首先正向建图 for (i = 1; i <= n; ++i){ scanf("%d",&out[i]); add(i,out[i]); } //tarjan缩点 for (i = 1; i <= n; ++i){ if (dfn[i] == -1){ top = 0; tarjan(i); } } CL(head,-1); ct = 0; for (int i = 0; i <= n; ++i){ f[i] = i; dis[i] = 0; indeg[i] = 0; up[i] = -1; } //重新建立逆向图 for (i = 1; i <= n; ++i){ int u = belong[out[i]]; int v = belong[i]; if (u != v){ add(u,v); indeg[v]++; if (cnt[u] > 1){//如果父节点是环就只想他们 up[v] = out[i]; } } else{//如果是环里的点并查集维护 int x = find(out[i]); int y = find(i); if (x != y){ f[y] = x; dis[y] = dis[out[i]] + 1; } } } //添加虚拟节点 for (i = 1; i < tot; ++i){ if (indeg[i] == 0){ add(0,i); } } } //dfs E R D dep; void dfs(int st,int h){ dep[st] = h; E[++top] = st; D[top] = h; R[st] = top; for(int i = head[st];i != -1;i = edge[i].next){ if(up[edge[i].v] == -1){ up[edge[i].v] = up[st]; } dfs(edge[i].v , h+1); E[++top] = st; D[top] = h; } return; } int Min(int i,int j){ if (D[i] < D[j]) return i; else return j; } int bound; //rmq的初始化 void init_rmq() { for (int i = 0; i < 30; ++i) pow2[i] = 1<<i; for(int i = 1;i <= top; ++i){ dp[i][0] = i; } for(int j = 1; pow2[j] <= top; ++j){ for(int i = 1; (i + pow2[j] - 1) <= top; ++i){ dp[i][j] = Min(dp[i][j-1],dp[i + (1 << (j-1))][j-1]); } } return; } int rmq(int l,int r){ int d = log((double)(r - l + 1)) / log(2.0); return Min(dp[l][d],dp[r - pow2[d] + 1][d]); } void solve() { dfs(0,0); init_rmq(); int u,v; while(m--) { scanf("%d%d",&u,&v); if(u == v) { puts("0 0"); continue; } int x = belong[u]; int y = belong[v]; int lca ;//= askrmq(bj[x] , bj[y]); if (R[x] <= R[y]) lca = E[rmq(R[x],R[y])]; else lca = E[rmq(R[y],R[x])]; if(lca == 0){//若果是0说明分别在两棵树上,不能到达 puts("-1 -1"); continue; } else if(cnt[lca] == 1){//如果是1说明在一棵树上,不存在公共祖先为环 printf("%d %d\n",dep[x] - dep[lca],dep[y] - dep[lca]); continue; } //公共祖先为环的情况 int dx,dy,dxy,dyx; dx = dep[x] - dep[lca]; dy = dep[y] - dep[lca]; u = (up[x] == -1) ? u : up[x]; v = (up[y] == -1) ? v : up[y]; find(u),find(v); if(dis[u] < dis[v]){ dxy = cnt[lca] + dis[u] - dis[v]; dyx = dis[v] - dis[u]; } else{ dxy = dis[u] - dis[v]; dyx = cnt[lca] - dxy; } if(MAX(dx+dxy , dy) < MAX(dx , dy+dyx)) printf("%d %d\n",dx+dxy , dy); else if(MAX(dx+dxy , dy) > MAX(dx , dy+dyx)) printf("%d %d\n",dx , dy+dyx); else { if(MIN(dx+dxy , dy) < MIN(dx , dy+dyx)) printf("%d %d\n",dx+dxy , dy); else if(MIN(dx+dxy , dy) > MIN(dx , dy+dyx)) printf("%d %d\n",dx , dy+dyx); else { if (dx+dxy > dy) printf("%d %d\n",dx+dxy , dy); else printf("%d %d\n",dx , dy+dyx); } } } return; } int main(){ //freopen("din.txt","r",stdin); while (~scanf("%d%d",&n,&m)){ makeEage(); solve(); } return 0; }