ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

洛谷P4216

2021-09-05 10:33:00  阅读:135  来源: 互联网

标签:洛谷 int 询问 dep P4216 LCA now sum


算法:树状数组+离线思想+倍增LCA

时间复杂度:\(O(nlogn)\)(假设点数,边数与询问数相同)

第一问显然,为两者距离加\(1\),可以用LCA算出。(加\(1\)是因为算的是点的个数,而两者距离则是边的个数)

第二问,设当前有一个询问\((x,y,c)\),当前时间为\(now\),考虑什么点才会对这个询问产生贡献。

不难得出,这个点需要满足两个条件

\(1\).这个点在\(x\)到\(y\)的路径上

\(2\).这个点有一个时间戳(即这个点最开始被派任务的时间)设为\(t\),且\(now-t>c\)(即算出这个点的现在的危险值与\(c\)比较)。

对第二个条件进行转化,变为\(now-c>t\),将不同属性(即\(now\)和\(c\)是现在的属性,\(t\)是过去的属性)的变量分开。

结合第一个条件,不难得出询问的本质:问\(x\)到\(y\)的路径上有多少个点的时间戳小于\(now-c\),而且带修改。

先将问题简化,考虑不带修(即最开始每个点都有一个时间戳)。

转化问题,设\(sum[x]\)表示从\(x\)到根这一条路径上小于当前询问\(now-c\)的点的个数,那么对于当前询问答案就是\(sum[x]+sum[y]-sum[LCA(x,y)]-sum[father[LCA(x,y)]]\)

考虑离线转化,将询问记录在每个点上,然后使用dfs遍历,遍历到一个点的时候同时处理询问即可。

具体来说,用一个数组\(a\)记录所有时间戳的个数,\(a[i]\)表示时间戳为\(i\)的点有多少个。

在dfs的时候,遍历到当前点\(i\),就让\(a[dfn[i]]++\),处理与\(i\)有关的询问,然后继续遍历,在从\(i\)回溯之前,就让\(a[dfn[i]]--\)。每次处理询问查询的都是一个区间和,用树状数组维护即可。每次刚开始对一个点处理询问时,树状数组记录的都是从\(i\)到根这一条路径上所有点的不同的时间戳个数。不难得出,这种方法是正确的。

事实上,这种树上离线的思想非常重要,各位可以记下来。另有一道题:NOIP天天爱跑步,用了类似的思想,感兴趣的可以去挑战一下

回到本题,带修怎么办?

观察\(now-c\),发现\(c\)为非负整数,所以在一个询问后面的修改的点的时间戳,并不能影响当前这个询问。所以我们可以一次性读入所有的时间戳,转化为上面的问题进行处理即可。

那如果\(c\)可以为负数怎么办?

不用想的太复杂,此时的答案显然就跟第一问的答案相同。

code

#define ll long long
#define ull unsigned long long
using namespace std;
const int N=2e5+10;
int n,m,q;
int dep[N],f[N][25],dis[N],ans[N],col[N],fa[N];
int End[N<<1],Last[N],Next[N<<1];
int c[N];
struct Node
{
	int dif,id,mark;
}temp;
vector<Node> query[N];
int read() 
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-48;s=getchar();}
    return x*f;
}
int tot;
void add(int x,int y)
{
	End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;
}
void deal_first(int x,int fa)
{
	dep[x]=dep[fa]+1;
	for(int i=1;i<=20;i++)
	f[x][i]=f[f[x][i-1]][i-1];
	for(int i=Last[x];i;i=Next[i])
	{
		int u=End[i];
		if(u==fa) continue;
		f[u][0]=x;
		deal_first(u,x);
	}
}
int LCA(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(dep[f[x][i]]>=dep[y])
		x=f[x][i];
		if(x==y) return x;
		if(dep[x]==dep[y]) break;
	}
	for(int i=20;i>=0;i--)
	if(f[x][i]!=f[y][i])
	{
		x=f[x][i];
		y=f[y][i];
	}
	return f[x][0];
}
void modify(int x,int d)
{
	for(;x<=q;x+=x&-x)
	c[x]+=d;
 } 
int ask(int x)
{
	int res=0;
	for(;x>=1;x-=x&-x)
	res+=c[x];
	return res;
}
void dfs(int x,int fa)
{
	if(col[x])
	modify(col[x],1);
	for(int i=0;i<query[x].size();i++)
	ans[query[x][i].id]+=ask(query[x][i].dif-1)*query[x][i].mark;
	for(int i=Last[x];i;i=Next[i])
	{
		int u=End[i];
		if(u==fa) continue;
		dfs(u,x);
	}
	if(col[x])
	modify(col[x],-1);
}
int main() 
{
	int cnt=0,Root;
	n=read();
	for(int i=1,u;i<=n;i++)
	{
		u=read();
		fa[i]=u;
		if(!u)
		{
			Root=i;
			continue;
		}
		add(u,i);
		add(i,u);
	} 
	deal_first(Root,0);
	q=read();
	int x,y,c;
	for(int i=1;i<=q;i++)
	{
		int op=read();
		if(op==1)
		{
			cnt++;
			x=read(),y=read();
			c=read();
			temp.id=cnt;
			temp.dif=i-c;
			temp.mark=1;
			query[x].push_back(temp);
			query[y].push_back(temp);
			temp.mark=-1;
			int lca=LCA(x,y);
			query[lca].push_back(temp);
			query[fa[lca]].push_back(temp);
			dis[cnt]=dep[x]+dep[y]-(dep[lca]<<1);
		}
		else col[read()]=i;
	} 
	dfs(Root,0);
	for(int i=1;i<=cnt;i++)
	printf("%d %d\n",dis[i]+1,ans[i]);
    return 0;
}

标签:洛谷,int,询问,dep,P4216,LCA,now,sum
来源: https://www.cnblogs.com/dingxingdi/p/15228750.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有