ZYF loves set (lca+转化)

【输入格式】
第一行一个正整数 n。
接下来 n行描述集合 A1,A2,...,An。第一个数表示集合中元素的个数,之后给出集合中的元素。
接下来一行包含一个正整数 q。
接下来 q行每行描述一个询问,格式与之前相同。
【输出格式】
对于每组询问,输出一个整数表示答案。
【样例输入】

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


【数据规模及约定】
对于 20%的数据,n,q<=50
对于 40%的数据,n,q<=1000
对于 100%的数据,n,q<=200000
输入文件大小不超过 15MB。


题解:题目中集合与集合的关系可以抽象成树的结构。

定义一个新集合就相当于是在一堆点的lca上在加入一个新节点

查询就是求一些点到根的路径上一共覆盖了多少点,可以利用dfs序以及点与点之间lca的深度关系来计算。

按dfs序排序,这样每个点都与与他距离最近的点相连,计算对答案的贡献的时候只需要+deep[now]-deep[lca(now,now-1)] 即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 10000003
#define M 500000
using namespace std;
int a[N],b[N],f[20][M],mi[20];
int point[M],next[M],v[M],pos[M],w[M],tot,cnt,size;
int n,m,t,l[N],deep[N],q[M],cur[M],st[M];
void add(int x,int y,int k)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; w[tot]=k;
	//cout<<x<<" "<<y<<" "<<k<<endl;
}
void change(int x,int y)
{
	deep[y]=deep[x]+1;
	f[0][y]=x;
	for (int i=1;i<=18;i++)
	 {
	 	if (deep[y]-mi[i]<0) break;
	 	f[i][y]=f[i-1][f[i-1][y]];
	 }
}
int lca(int x,int y)
{
	if (deep[x]<deep[y])  swap(x,y);
	int k=deep[x]-deep[y];
	for (int i=0;i<=18;i++)
	 if ((k>>i)&1)
	   x=f[i][x];
	if (x==y) return y;
	for (int i=18;i>=0;i--)
	 if (f[i][x]!=f[i][y])
	  {
	  	x=f[i][x];
	  	y=f[i][y];
	  }
	return f[0][x];
}
void dfs(int x)
{
	q[x]=++size;
	for (int i=point[x];i;i=next[i])
	 dfs(v[i]);
}
void dfs1()
{
	int top=0;
	for (int i=0;i<=n;i++) cur[i]=point[i];
	st[++top]=0;
	while(top)
	{
		int x=st[top];
		if (!cur[x])
		 {
		 	--top;
		 	q[x]=++size;
		 	continue;
		 }
		int vt=v[cur[x]];
		st[++top]=vt; cur[x]=next[cur[x]];
	}
}
int cmp(int x,int y)
{
	return q[x]<q[y];
}
int main()
{
	freopen("set.in","r",stdin);
	freopen("set.out","w",stdout);
	scanf("%d",&n);
	mi[0]=1;
	for (int i=1;i<=19;i++) mi[i]=mi[i-1]*2;
	for (int i=1;i<=n;i++)
	 {
	 	int x; scanf("%d",&x);  l[i]=l[i-1]+x;
	 	for (int j=l[i-1]+1;j<=l[i];j++)
	 	  scanf("%d",&a[j]);
	 }
	for (int i=1;i<=n;i++)
	 {
	 	if (l[i-1]-l[i]==0)
	 	 {
	 	 	pos[i]=++cnt;
	 	 	add(0,cnt,i);
	 	 	change(0,cnt);
	 	 	continue;
	 	 }
	 	if (l[i]-l[i-1]==1)
	 	 {
	 	 	pos[i]=++cnt;
	 	 	add(pos[a[l[i]]],cnt,i);
	 	 	change(pos[a[l[i]]],cnt);
	 	 	continue;
	 	 }
	 	int t=lca(pos[a[l[i-1]+1]],pos[a[l[i-1]+2]]);
	 	for (int j=l[i-1]+3;j<=l[i];j++)
	 	 	t=lca(pos[a[j]],t);
	 	pos[i]=++cnt;
	 	add(t,cnt,i);
	 	change(t,cnt);
	 }
	//dfs(0);
	dfs1();
	//for (int i=1;i<=n;i++)
	 //cout<<q[i]<<" ";
	//cout<<endl;
	scanf("%d",&m);
	for (int i=1;i<=m;i++)
	{
		int x; scanf("%d",&x);
		for (int j=1;j<=x;j++)
		 scanf("%d",&b[j]);
		sort(b+1,b+x+1,cmp);
		//for (int j=1;j<=x;j++)
		// cout<<b[j]<<" ";
		//cout<<endl;
		int ans=0;
		ans+=deep[b[1]];
		for (int j=2;j<=x;j++)
		 ans+=deep[b[j]]-deep[lca(b[j],b[j-1])];
		printf("%d\n",ans);
	} 
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值