[BZOJ 2815][ZJOI 2012] 灾难 LCA+拓扑排序(支配树)

题目传送门:【BZOJ 2815】
题目描述:【BZOJ 2815 题面】


题目大意: 我们用一种叫做食物网的有向图来描述生物之间的关系:
一个食物网有 N 个点,代表 N 种生物,如果生物 x 可以吃生物 y,那么从 y
向 x 连一个有向边。
这个图没有环。

图中有一些点没有入边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有入边的点代表的都是消费者,它们必须通过吃其他生物来生存。如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
一个图里面可能会有多个生产者。

我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。现在,我们给定了一个食物网,你要求出每个生物的灾难值。


题目分析:

这道题和我们校内的一道模拟题十分相似,只不过那道题是求从源点到目标点的“关键点”个数,这里是求一个点是多少个其他点的“关键点”。

分析题意。如果我们只考虑整个图是一棵树的情况,那么显然,对于任意一个点 ,它的灾难值就是它的子树大小-1(子树当然是要包括自己的)。但是这里,它并不是一棵树,而是一个 DAG(有向无环图)。

DAG 图和树形图有许多的共性,它们也有很多相似之处。到了这里,我们可能会想:应该怎么样才能去把 DAG 图的“食物网”关系给弄出来呢?或者,我们能不能把 DAG 图转化为一棵树,然后再去求它的子树大小,进而得到答案呢?

幸运的是,这样做是可行的。我们可以利用整个图的拓扑关系,通过拓扑排序的方式遍历整个图。在遍历整个图的时候,我们发现:如果有这么一个点 q,它可以由其他多个点 p 1 ,p 2 ,…… p n 遍历到,那么,它只会直接受到所有的这些点的 LCA 所影响(即:只有 LCA( p 1 ,p 2 ,…… p n ) 的灭绝,才会使得点 q 代表的生物灭绝)。

所以,在进行拓扑排序的时候,我们需要记录下将要遍历到的那个点的 father(即:这个 father 是它的最近“关键点”,当这个 father 灭绝的时候,它也会跟着灭绝);如果它已经被记录下了一个 father,那么我们就尝试去更新它,最后得到的就是它的直接 father。同时,当一个节点得到了直接 father 时,我们就对它的直接 father 和这个点连一条有向边。最后,当整个拓扑排序结束时,DAG 图就被转化成了一个树形图。

这样的树形图又被叫做“支配树”,它就是由一系列变换而得到的一棵新树。对于这道题,由于原图仅仅是一个 DAG,所以生成支配树的方式有所不同,我们只需要考虑原图的拓扑关系和点与点之间的关系即可。

对于新的树形图,我们再跑一遍 DFS,就可以求出每个点的子树大小。此时我们就把原问题转化成了树形图上的问题。

但是这里有一个细节:一个图里面可能会有多个生产者,这就意味着,原图进行变换之后,得到的不一定是一棵树,而是一片森林(多棵树)。对于这种情况,在原图中,我们新加入一个超级源点,然后把超级源点和最开始时所有入度为 0 的点相连,在拓扑排序之后,一定就能得到一棵树了。然后我们以超级源点为根,再来跑 DFS 即可得到正确答案。

下面附上代码:

[cpp] view plain copy
print ?
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. using namespace std;  
  6. typedef long long LL;  
  7. const int MX=70005;  
  8. const int P=16;  
  9.   
  10. struct Edge{  
  11.     int to,next;  
  12. }edge[MX*2],trees[MX*2];  
  13. int n,m,head[MX],now=0;  
  14. queue<int> q;  
  15. int fa[MX][P+2],deg[MX],dep[MX],ans[MX],getlca(int,int);  
  16. int head2[MX],now2=0,siz[MX];  
  17. bool vis[MX],vis2[MX];  
  18.   
  19. inline void adde(int u,int v){  
  20.     edge[++now].to=v;  
  21.     edge[now].next=head[u];  
  22.     head[u]=now;  
  23. }  
  24. inline void adde2(int u,int v){                             //在新得到的树上建图   
  25.     trees[++now2].to=v;  
  26.     trees[now2].next=head2[u];  
  27.     head2[u]=now2;  
  28. }  
  29. void bfs(int s){                                            //拓扑排序   
  30.     q.push(s);  
  31.     while (!q.empty()){  
  32.         int u=q.front();  
  33.         q.pop();  
  34.         for (int i=head[u];i;i=edge[i].next){  
  35.             int v=edge[i].to;  
  36.             if (!vis[v]) vis[v]=true,fa[v][0]=u;  
  37.             else {  
  38.                 fa[v][0]=getlca(fa[v][0],u);  
  39.             }  
  40.             deg[v]–;  
  41.             if (!deg[v]){  
  42.                 q.push(v);  
  43.                 adde2(fa[v][0],v);  
  44.                 dep[v]=dep[fa[v][0]]+1;                     //这里是fa[v][0],因为u不一定是转移到v的那个点   
  45.                 for (int i=1;i<=P;i++)                       //fa[v][0]是支配树实际的转移点   
  46.                     fa[v][i]=fa[fa[v][i-1]][i-1];  
  47.             }  
  48.         }  
  49.     }  
  50. }  
  51. void dfs(int u){                                            //在建好的新树上跑dfs求子树大小   
  52.     siz[u]=1;  
  53.     vis2[u]=true;  
  54.     for (int i=head2[u];i;i=trees[i].next){  
  55.         int v=trees[i].to;  
  56.         if (vis2[v]) continue;  
  57.         dfs(v);  
  58.         siz[u]+=siz[v];  
  59.     }  
  60. }  
  61. int getlca(int a,int b){                                    //倍增跳 LCA   
  62.     if (dep[a]
  63.     int t=dep[a]-dep[b];  
  64.     for (int i=P;i>=0;i–){  
  65.         if (fa[a][i] && t&(1<
  66.             a=fa[a][i];  
  67.     }  
  68.     if (a==b) return a;  
  69.     for (int i=P;i>=0;i–){  
  70.         if (fa[a][i] && fa[b][i] && fa[a][i]!=fa[b][i])  
  71.             a=fa[a][i],b=fa[b][i];  
  72.     }  
  73.     return fa[a][0];  
  74. }  
  75.   
  76. int main(){  
  77.     scanf(”%d”,&n);  
  78.     for (int i=1;i<=n;i++){  
  79.         while (1){  
  80.             scanf(”%d”,&m);  
  81.             if (m==0) break;  
  82.             adde(m+1,i+1);                                  //把所有节点编号+1,之后将超级源点设为 1 号点   
  83.             deg[i+1]++;                                     //防止编号溢出   
  84.         }  
  85.     }  
  86.     for (int i=P;i>=0;i–) fa[1][i]=0;  
  87.     dep[1]=1;  
  88.     q.push(1);  
  89.     for (int i=1;i<=n;i++)  
  90.         if (deg[i+1]==0) deg[i+1]++,adde(1,i+1);  
  91.     bfs(1);  
  92.     dfs(1);  
  93.     for (int i=1;i<=n;i++){  
  94.         printf(”%d\n”,siz[i+1]-1);  
  95.     }  
  96.     return 0;  
  97. }  

你可能感兴趣的:(各大OJ专题(POJ,BZOJ,hdu等),遍历,拓扑排序,LCA问题)