洛谷P4216

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

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

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

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

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

1.这个点在xy的路径上

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

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

结合第一个条件,不难得出询问的本质:问xy的路径上有多少个点的时间戳小于nowc,而且带修改。

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

转化问题,设sum[x]表示从x到根这一条路径上小于当前询问nowc的点的个数,那么对于当前询问答案就是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天天爱跑步,用了类似的思想,感兴趣的可以去挑战一下

回到本题,带修怎么办?

观察nowc,发现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;
}
posted @   最爱丁珰  阅读(28)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示