[灾难树] BZOJ 2815 2851

灾难树 一般的做法 先拓扑 再建树 求lca确定father 根对于子树中所有点都是灾难点

BZOJ 2815

以下是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;
}

BZOJ 2851

不用拓扑 这里求的是一些个点的灾难点的并集

就是求一些点到根的路径的并 注意去重

#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;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值