以下是fhq神犇的题解
《灾难》
【题意简述】
给一个有向无环图,问每个节点删掉之后会导致多少个点不可达。
【算法描述】
有这样一个事实:
生物之间的灭绝的结构形成了一个树,树上的一个节点的灭绝会且仅会导致以它为根的子树的灭绝。我们管这个树叫“灭绝树”。
对于生产者,我们给它添加一个假想的食物:太阳。
这样,“灭绝树”就形成了一个以太阳为根的树。
下面说明如何通过增量法把灭绝树建出来,同时也是对灭绝树的存在性的证明。
首先,把食物网按从猎物到捕食者的顺序拓扑排序。
之后,依次考虑每个生物i.我们已经构建好了排序在i之前的生物组成的“灭绝树”了。
假设i的食物有x[0]~x[k](x[0]~x[k]在拓扑排序中比i靠前)。
很显然,只有x[0]~x[k]在树上的公共祖先的灭绝会导致i灭绝,否则i一定可以找到能让它活下来的食物。
...
/
/
LCA
/|\
_/ ||
/ | \
O | \
/ \ \ \
x x x x
i
于是,我们可以把i挂在x[0]~x[k]的最近公共祖先下面。
处理完所有的生物,我们得到的树就是整个图的灭绝树了。
一旦得到灭绝树,每个生物的灾难值就可以通过以它为根的子树的大小减1来计算.
【复杂度分析】
拓扑排序的时间复杂度是O(|E|)的。
一共有|E|次LCA的查询,和|V|次添加边的操作。
我们使用某种支持快速查询LCA、添加点的数据结构(例如动态树)。
这样,总的时间复杂度是O(|E|log|V|)。
动态树什么的,我还是用倍增。。
#include<cstdio> #include<cstdlib> #include<algorithm> #include<vector> #include<cstring> #define V G[p].v #define cl(x) memset(x,0,sizeof(x)) using namespace std; inline char nc() { static char buf[100000],*p1=buf,*p2=buf; if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; } return *p1++; } inline void read(int &x) { char c=nc(),b=1; for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1; for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b; } struct edge{ int u,v; int next; }; edge G[5000005]; int head[70005],num; inline void add(int u,int v,int p) { G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p; } int n; int lst[70005],pnt,vst[70005]; inline void dfs(int u) { vst[u]=1; for (int p=head[u];p;p=G[p].next) if (!vst[V]) dfs(V); lst[pnt--]=u; } int parent[70005][25],depth[70005]; inline int LCA(int u,int v) { if (depth[u]<depth[v]) swap(u,v); for (int k=20;k>=0;k--) if (((depth[u]-depth[v])>>k)&1) u=parent[u][k]; if (u==v) return u; for (int k=20;k>=0;k--) if (parent[u][k]!=parent[v][k]) u=parent[u][k],v=parent[v][k]; return parent[u][0]; } vector<int> food[70005]; int size[70005]; inline void count(int u) { size[u]=1; for (int p=head[u];p;p=G[p].next) count(V),size[u]+=size[V]; } int main() { int tmp; freopen("t.in","r",stdin); freopen("t.out","w",stdout); read(n); for (int i=1;i<=n;i++) { read(tmp); if (tmp) for (;tmp;read(tmp)) food[i].push_back(tmp),add(tmp,i,++num); else food[i].push_back(n+1),add(n+1,i,++num); } pnt=++n; dfs(n); cl(G); cl(head); num=0; depth[n]=1; for (int i=2;i<=n;i++) { int lca=0,u=lst[i]; for (int j=0;j<food[u].size();j++) if (lca==0) lca=food[u][j]; else lca=LCA(lca,food[u][j]); add(lca,u,++num); depth[u]=depth[lca]+1; parent[u][0]=lca; for (int k=1;k<=20;k++) parent[u][k]=parent[parent[u][k-1]][k-1]; } count(n); for (int i=1;i<n;i++) printf("%d\n",size[i]-1); return 0; }
不用拓扑 这里求的是一些个点的灾难点的并集
就是求一些点到根的路径的并 注意去重
#include<cstdio> #include<cstdlib> #include<algorithm> #include<vector> #include<cstring> #define V G[p].v #define cl(x) memset(x,0,sizeof(x)) using namespace std; inline char nc() { static char buf[100000],*p1=buf,*p2=buf; if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; } return *p1++; } inline void read(int &x) { char c=nc(),b=1; for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1; for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b; } struct edge{ int u,v; int next; }; edge G[500005]; int head[200005],num; inline void add(int u,int v,int p) { G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p; } int n; int parent[200005][25],depth[200005]; inline int LCA(int u,int v) { if (depth[u]<depth[v]) swap(u,v); for (int k=20;k>=0;k--) if (((depth[u]-depth[v])>>k)&1) u=parent[u][k]; if (u==v) return u; for (int k=20;k>=0;k--) if (parent[u][k]!=parent[v][k]) u=parent[u][k],v=parent[v][k]; return parent[u][0]; } vector<int> food[200005]; vector<int> query[200005]; int last[200005],ans[200005]; inline void dfs(int u) { for (int i=0;i<query[u].size();i++) { int q=query[u][i]; if (!last[q]) ans[q]+=depth[u]-1; else ans[q]+=depth[u]-depth[LCA(u,last[q])]; last[q]=u; } for (int p=head[u];p;p=G[p].next) dfs(V); } int main() { int tmp,tot,Q; freopen("t.in","r",stdin); freopen("t.out","w",stdout); read(n); for (int i=1;i<=n;i++) { read(tot); if (tot) while (tot--) read(tmp),food[i].push_back(tmp); else food[i].push_back(n+1); } ++n; depth[n]=1; for (int i=1;i<n;i++) { int lca=0,u=i; for (int j=0;j<food[u].size();j++) if (lca==0) lca=food[u][j]; else lca=LCA(lca,food[u][j]); add(lca,u,++num); depth[u]=depth[lca]+1; parent[u][0]=lca; for (int k=1;k<=20;k++) parent[u][k]=parent[parent[u][k-1]][k-1]; } read(Q); for (int i=1;i<=Q;i++) { read(tot); while (tot--) read(tmp),query[tmp].push_back(i); } dfs(n); for (int i=1;i<=Q;i++) printf("%d\n",ans[i]); return 0; }