2018-10-25杂题选讲

2018-10-25杂题选讲

T5 灾难

原题
一个食物网有N个点,代表N种生物,如果生物x可以吃生物y,那么从y向x连一个有向边。
这个图没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。

输入格式:

第一行点数N
接下来 N 行,每行描述了一个生物可以吃的其他生物的列表,格式为用空
格隔开的若干个数字,每个数字表示一种生物的标号,最后一个数字是 0 表示列
表的结束。

输出格式:

每行一个整数,表示每个生物的灾难值。

x指向y表示x吃y,那么若一个点p灭绝了意味着它没东西可吃,不能从p走到任何一个叶子。
那么对于一个命题,“已知若A灭绝则B灭绝,若B灭绝则C灭绝,那么若A灭绝则C灭绝”,显然成立。
这句话听起来就像“A能到B,B能到C,所以A能到C”,于是我们考虑建一个有向图,每条有向边表示若起点死了,终点也活不成,那么建出来的图一定是一棵树
那么如果在这棵树上找答案就简单了,每个点的答案就是size[u]-1。
建树:若该节点只有一个父亲(这个父亲指反向的边,消费者指向生产者),则两点之间连一条边;若该节点有多个父亲,则将该节点与这些父亲的lca相连。从生产者开始慢慢建出这棵树(生产者为根,因为生产者死了其他人全挂),若有多个生产者则都连上n+1这个点。
拓扑排序:在把某个点加入树时,它的所有父亲必须已经在树中,相当于一层一层建这棵树,符合拓扑排序的思想,于是考虑先处理出建树的顺序。

提交记录

#include<bits/stdc++.h>
using namespace std;
long long n,tmp1,first1[700070],nxt1[7000070],first2[700070],nxt2[700070],u1[7000070],v1[7000070],u2[7000070],v2[7000070],tot1=0,tot2=0;
long long topork[700070],degree[70070]={0},line[700070],l=1,r=0,tmp2,cnt=0;
long long maxn,tmp3,grand[700070][50]={0},lca,depth[700070]={0},size[700070];
//1为消费者指向生产者(吃它)
//2为生产者指向消费者(我死了它没东西吃)
void add1(long long from,long long to)
{
	tot1++;
	nxt1[tot1]=first1[from];
	first1[from]=tot1;
	u1[tot1]=from;
	v1[tot1]=to;
	return;
}
void add2(long long from,long long to)
{
	tot2++;
	nxt2[tot2]=first2[from];
	first2[from]=tot2;
	u2[tot2]=from;
	v2[tot2]=to;
	for (long long i=1;i<=maxn;i++) grand[to][i]=grand[grand[to][i-1]][i-1];
	return;
}
void TOPO()
{
	for (long long i=1;i<=n;i++) if (!degree[i]) line[++r]=i;
	while (l<=r)
	{
		tmp2=line[l++];
		topork[++cnt]=tmp2;
		for (long long j=first1[tmp2];j!=-1;j=nxt1[j])
		{
			
			degree[v1[j]]--;
			if (!degree[v1[j]]) line[++r]=v1[j];
		}
	}
}
long long LCA(long long a,long long b)
{
	if (depth[a]<depth[b]) swap(a,b);
	while (depth[a]!=depth[b])
	{
		int t=log2(depth[a]-depth[b]);
		a=grand[a][t];
	}
	for (long long i=maxn;i>=0;i--)
	{
		if (grand[a][i]!=grand[b][i])
		{
			a=grand[a][i];
			b=grand[b][i];
		}
	}
	if (a!=b) a=grand[a][0];
	return a;
}
void build()
{
	grand[n+1][0]=n+1;
	for (long long i=n;i>=1;i--)
	{
	    tmp3=topork[i];
	    if (first1[tmp3]==-1)
	    {
		    grand[tmp3][0]=n+1;
		    depth[tmp3]=1;
	    	add2(n+1,tmp3);
			continue;
	    }
	    lca=v1[first1[tmp3]];
	    for (long long j=first1[tmp3];j!=-1;j=nxt1[j]) lca=LCA(lca,v1[j]);
	    grand[tmp3][0]=lca;
	    depth[tmp3]=depth[lca]+1;
	    add2(lca,tmp3);
	}
	return;
}
void dfs(long long point)
{
	size[point]=1;
	for (long long j=first2[point];j!=-1;j=nxt2[j])
	{
		long long to=v2[j];
		dfs(v2[j]);
		size[point]+=size[v2[j]];
	}
	return;
}
int main()
{
	memset(first1,-1,sizeof(first1));
	memset(first2,-1,sizeof(first2));
	scanf("%lld",&n);
	maxn=log2(n);
	for (long long i=1;i<=n;i++)
	{
		scanf("%lld",&tmp1);
		while (tmp1)
		{
			add1(i,tmp1);
			degree[tmp1]++;
			scanf("%lld",&tmp1);
		}
	}
    TOPO();
    build();
    dfs(n+1);
    for (long long i=1;i<=n;i++) printf("%lld\n",size[i]-1);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值