codeforces #329 D. Happy Tree Party (LCA+并查集 || 树链剖分)

本文解析了CodeForces竞赛中一道涉及树结构查询与更新的难题。提出了两种解法,一种通过路径压缩优化查询效率,另一种利用树链剖分与线段树实现区间乘积的快速计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:http://codeforces.com/contest/593/problem/D

题意:给定一棵n个节点的树。有两种操作:①给定 u v p, 求p除u--->v这条链上所有的边权的值(每次上下取整)②修改某一条边的边权。

分析:

解法一:由于p<=10^18,所以p最多除以60个大于等于2的数就会变为0,当我们遍历一条链的时候只要能避开那些边权为1的边就行了。为每个节点添加一个索引,指向第一个不为1的祖先。初始的时候每个节点的索引指向父亲节点,每次查询的时候顺便压缩一下路径就行了。

具体做法:u-->lca (deep(u)>deep(lca)), v-->lca (deep(v)>deep(lca))

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL INF = 1E9+9;
const int maxn = 4e5+6;

struct node
{
	int v,next;
	LL w;
}List[maxn];
int head[maxn],cnt;
void add(int u,int v,LL w)
{
	List[cnt].v=v;
	List[cnt].w=w;
	List[cnt].next=head[u];
	head[u]=cnt++;
}

int anc[200009][20],deep[200009],index[200009];
LL fw[maxn];

void dfs(int cur,int dp)
{
	deep[cur]=dp;
	for(int i=head[cur];~i;i=List[i].next)
	{
		int son=List[i].v;
		if(anc[cur][0]!=son)
		{
			anc[son][0]=cur;
			fw[son]=List[i].w;
			for(int j=1;j<20;j++)
				anc[son][j]=anc[anc[son][j-1]][j-1];
			dfs(son,dp+1);
		}
	}
}

int LCA(int a,int b)
{
	if(deep[a]<deep[b])
		swap(a,b);
	int h=deep[a]-deep[b];
	for(int i=0;i<20;i++)
		if((h>>i)&1)
			a=anc[a][i];
	if(a==b) return a;
	for(int i=19;i>=0;i--)
	{
		if(anc[a][i]!=anc[b][i])
		{
			a=anc[a][i];
			b=anc[b][i];
		}
	}
	return anc[a][0];
}

pair <int,int> data[maxn];
int main()
{
	int n,q,i,j,k,u,v,x,y,tp;
	LL w,p;
	scanf("%d%d",&n,&q);
	memset(head,-1,sizeof(head));
	for(i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&w);
		data[i]=make_pair(u,v);
		add(u,v,w);add(v,u,w);
	}
	for(i=1;i<=n;i++)
		anc[i][0]=1;
	dfs(1,0);
	for(int i=1;i<=n;i++)  //初始指向父亲节点 
		index[i]=anc[i][0];
	while(q--)
	{
		scanf("%d",&tp);
		if(tp==1)
		{
			scanf("%d%d%lld",&x,&y,&p);
			int lca=LCA(x,y);
		//	printf("lca(%d %d):%d\n",x,y,lca);
			while(deep[x]>deep[lca] && p>0)
			{
				p/=fw[x];
				x=(fw[index[x]]>1?index[x]:(index[x]=index[index[x]])); //路径压缩 
			}
			while(deep[y]>deep[lca] && p>0)
			{
				p/=fw[y];
				y=(fw[index[y]]>1?index[y]:(index[y]=index[index[y]]));
			}
			printf("%lld\n",p);
		}
		else
		{
			scanf("%d%lld",&x,&p);
			u=data[x].first;
			v=data[x].second;
			if(anc[u][0]==v)
				swap(u,v);
			fw[v]=p;	 
		}
	}
	return 0;
}

解法二:

floor(floor(x/n)/m)=floor(x/(nm))

直接树链剖分,用线段数维护区间乘积,注意溢出。

代码:

#include <bits/stdc++.h>
using namespace std;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef long long LL;
const LL INF = 1e18;
const int maxn = 2e5+7;
int head[maxn],cnt;
struct node
{
	int v,next;
}List[maxn<<2];
void add(int u,int v)
{
	List[cnt].v=v;
	List[cnt].next=head[u];
	head[u]=cnt++;
}

int top[maxn],deep[maxn],sz[maxn],fa[maxn],son[maxn],p[maxn],fp[maxn],pos;
int dfs1(int cur,int dp)
{
	deep[cur]=dp;
	sz[cur]=1;
	son[cur]=-1;
	for(int i=head[cur];~i;i=List[i].next)
	{
		int to=List[i].v;
		if(fa[cur]!=to)
		{
			fa[to]=cur;
			sz[cur]+=dfs1(to,dp+1);
			if(son[cur]==-1 || sz[to]>sz[son[cur]])
				son[cur]=to; 
		}
	}
	return sz[cur];
}
void dfs2(int cur,int sp)
{
	top[cur]=sp;
	p[cur]=pos++;
	fp[p[cur]]=cur;
	if(son[cur]==-1)
		return ;
	dfs2(son[cur],sp);
	for(int i=head[cur];~i;i=List[i].next)
	{
		int to=List[i].v;
		if(to!=son[cur] && fa[cur]!=to)
			dfs2(to,to);
	}
}

LL tree[maxn<<2];

LL Mul(LL a,LL b)
{
	if(a==0 || b==0 || INF/a<b)	return 0;
	return a*b;
}

void update(int pos,LL v,int l,int r,int rt)
{
	if(l==r)
	{
		tree[rt]=v;
		return ;
	}
	int m=(l+r)>>1;
	if(pos<=m)
		update(pos,v,lson);
	else
		update(pos,v,rson);
	tree[rt]=Mul(tree[rt<<1],tree[rt<<1|1]);
}
LL query(int L,int R,int l,int r,int rt)
{
	if(L<=l && r<=R)
		return tree[rt];
	int m=(l+r)>>1;
	LL ret=1;
	if(L<=m)
		ret=Mul(ret,query(L,R,lson));
	if(R>m)
		ret=Mul(ret,query(L,R,rson));
	return ret;
}
LL Q(int L,int R)
{
	return query(L,R,1,pos,1);
}

LL solve(int u,int v)
{
	int f1=top[u],f2=top[v];
	LL ret=1;
	while(f1!=f2)
	{
		if(deep[f1]<deep[f2])
		{
			swap(f1,f2);
			swap(u,v);
		}
		ret=Mul(ret,Q(p[f1],p[u]));
		if(!ret)	return 0;
		u=fa[f1];f1=top[u];
	}
	if(u==v)	return ret;
	if(deep[u]>deep[v])
		swap(u,v);
	return Mul(ret,Q(p[son[u]],p[v]));
}

LL in[maxn][3];
int main()
{
	int n,m,i,j,x,y;
	LL w;
	scanf("%d%d",&n,&m);
	memset(head,-1,sizeof(head));
	for(i=1;i<n;i++)
	{
		scanf("%d%d%lld",&x,&y,&w);
		add(x,y);add(y,x);
		in[i][0]=x;in[i][1]=y;in[i][2]=w;
	}
	pos=1;
	dfs1(1,1);
	dfs2(1,1);
	for(i=1;i<n;i++)
	{
		if(deep[in[i][0]]>deep[in[i][1]])
			swap(in[i][0],in[i][1]);
		update(p[in[i][1]],in[i][2],1,pos,1);
	}
	while(m--)
	{
		int tp;
		LL val;
		scanf("%d",&tp);
		if(tp==1)
		{
			scanf("%d%d%lld",&x,&y,&val);
			LL temp=solve(x,y);
			if(temp==0)	puts("0");
			else printf("%lld\n",val/temp);
		}
		else
		{
			scanf("%d%lld",&x,&w);
			update(p[in[x][1]],w,1,pos,1);
		} 
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值