洛谷 T281315 掌控

PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 T281315 掌控

题目

题目描述

公元 2044 年,人类进入了宇宙纪元。L 国有 n n n 个星球,分别编号为 1 1 1 n n n ,每一星球上有一个球长。有些球长十分强大,可以管理或掌控其他星球的球长,具体来说,第 i i i 个星球的球长管理 k i + 1 k_i+1 ki+1 个星球的球长,分别是 a i 1 , a i 2 , . . . , a i k i ( a i j < i ) a_{i1},a_{i2},...,a_{ik_i} (a_{ij}<i) ai1,ai2,...,aiki(aij<i) ,但若想要掌控一个星球的球长,就没那么容易了,第 i i i 个星球的球长掌控第 j j j 个星球的球长当且仅当他管理的所有球长都掌控第 j j j 个星球的球长,当然,所有球长都掌控他自己。
​这些球长要召开 q q q 次会议,每次会议由 t i t_i ti 个球长召开,所有被他们掌控的球长都会参加,你作为宇宙会议室室长,需要知道每次会议有多少个球长参加。

输入格式

第一行一个数 n n n ,表示星球的个数;
接下来 n n n 行,每一行首先给出一个 k i k_i ki (可能为 0 0 0 ),接下来 k i k_i ki 个数,描述第 i 个星球的球长管理的球长。保证没有重复;
接下来一行,给出一个数 q q q ,表示询问的个数;
接下来 q q q 行,每一行描述一个询问:格式同上文的格式。不保证没有重复(重复的球长当做只出现了一次)

输出格式

输出共 q q q 行,第 i i i 行输出第 i i i 次询问的答案。

样例

输入

7
0
1 1
1 1
1 2
2 2 3
0
2 2 6
3
2 2 3
2 3 5
2 4 5

输出

3
3
4

提示

对于第一个询问,2、3号球长都掌控1号球长,所以总共有3个球长参会,编号分别为1,2,3;
对于第二个询问,3、5号球长都掌控1号球长,所以总共有3个球长参会,编号分别为1,3,5;
对于第三个询问,4号球长掌控第1、2号球长,所以总共有4个球长参会,编号分别为1,2,4,5;
特别说明:第5号球长没有掌控球长2,因为 3 ∈ k 5 3∈k_5 3k5,但 2 2 2 不属于 k 3 k_3 k3 。但球长4掌控球长2,因为球长掌控自己。
    img
图片说明: u − > v u->v u>v 表示 v v v 管理 u u u

题解

首先,我们先用数学归纳法来证明一下他们之间的掌控关系是一棵树:

  1. 当只有一个节点时,它是一棵树。
  2. 假设他们之间的掌控关系已经是一棵树时,我们向里面加入一个节点,那么它可以掌控的节点就是所有它管理的节点的最近公共祖先到根节点之间的所有节点,我们就把这个节点加在这个最近公共祖先下面,那他们还是一棵树。
  3. 综上,他们之间的掌控关系是一棵树。

所以,要求他们的掌控的球长,就建一棵树就好了。
比如,样例建出来的树就长这样:
样例的树
然后,我们从根节点“0”深搜遍历并打上dfn。
样例的树打上dfn以后是这样的:
打上dfn后样例的树
再将所有召开的球长序号按dfn序排序,总的参会个数就是每次加上当前球长到当前球长和排序后的前一个球长的最近公共祖先之间的球长个数(即dep差)
样例的询问三的查询大概添加过程是这样的:
样例中询问三的添加过程

代码实现

//洛谷 T281315 掌控
#pragma GCC optimize(3)
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n,q;//200000
int k,t;//2000000
int x;
vector<int> v[200010];
int fa[200010][31];
int dep[200010];
int dfn[200010];
int cnt;
int a[2000010];
int ans;

void in(int &x){
	int nt;
	x=0;
	while(!isdigit(nt=getchar()));
	x=nt^'0';
	while(isdigit(nt=getchar())){
		x=(x<<3)+(x<<1)+(nt^'0');
	}
}

void dfs(int x){
	dfn[x]=++cnt;
	int sz=v[x].size();
	for(int i=0;i<sz;++i){
		dfs(v[x][i]);
	}
}

int lca(int x,int y){
	if(dep[x]>dep[y]){
		swap(x,y);
	}
	int tmp=dep[y]-dep[x],ans=0;
	for(int j=0;tmp;++j,tmp>>=1){
		if(tmp&1){
			y=fa[y][j];
		}
	}
	if(y==x){
		return x;
	}
	for(int j=30;j>=0&&y!=x;--j){
		if(fa[x][j]!=fa[y][j]){
			x=fa[x][j];
			y=fa[y][j];
		}
	}
	return fa[x][0];
}

bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}

int main(){
	register int i,j;
	in(n);
	for(i=1;i<=n;++i){
		in(k);
		if(k<1){
			t=0;
		}
		else{
			in(t);
		}
		for(j=2;j<=k;++j){
			in(x);
			t=lca(t,x);
		}
		v[t].push_back(i);
		fa[i][0]=t;
		dep[i]=dep[fa[i][0]]+1;
		for(int j=1;j<31;++j){
			fa[i][j]=fa[fa[i][j-1]][j-1];
		}
	}
	dfs(0);
	in(q);
	for(i=1;i<=q;++i){
		in(t);
		if(t==0){
			printf("0\n");
			continue;
		}
		for(j=1;j<=t;++j){
			in(a[j]);
		}
		sort(a+1,a+t+1,cmp);
		ans=dep[a[1]];
		for(j=2;j<=t;++j){
			ans+=dep[a[j]]-dep[lca(a[j-1],a[j])];
		}
		printf("%d\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月半流苏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值