CF 593D LCA,贪心

本文提供了一道 Codeforces 平台上的算法题 593D 的详细解答。该题涉及树形结构上的路径计算与边权更新。通过使用 LCA 查询与特殊的递归技巧优化了查询过程,实现 O(nlogc) 的时间复杂度。

题目链接:http://codeforces.com/problemset/problem/593/D


题意:给你一个含有n个点的树(2<=n<=200000),每个边有权值xi(1<=xi<=10^18),有m个询问(1<=m<=200000),每个询问别分是:1.给你一个c,从a点走到b点,每经过一个边c = c / xi,c为整数,向下取整,输出最后c的值。2.改变某条边的值,改变的值一定要比原来小,且大于等于1。


思路:对于询问a,b,找出其公共祖先,然后从任意一点出发向祖先走,走的过程中更新c值。因为边值要等于1,要么大于1。对于所有大于1的边,显然这种边最多走64次可以结束。对于等于1的边,向上递归到边值不为1的点,并把原来点的父亲直接标记到这个点,下次往上递归时直接到这个点,那么以后的向上递归次数也不会很多。复杂度为O(nlogc)。。。。。ps:比赛时没发现自己lca的错误,一直在调。。。后来过了pretest又在找父亲这细节写错。。。真是残念啊。。。。



#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn = 200005;

int n,m;
struct ppp
{	
	int v,nex;
	ll c;
}e[maxn * 4];
ll c;
int head[maxn];
int dp[maxn * 2][19],tole;
int dep[maxn],pos[maxn],last[maxn * 2],pre[maxn];
int qian[maxn];
int cnt;
void make_edge(int u,int v,ll c)
{
	e[tole].c = c;e[tole].v = v,e[tole].nex = head[u];head[u] = tole++;
}
void dfs(int u,int fa,int deep)
{
	dep[u] = deep;
	pos[u] = ++cnt;
	last[cnt] = u;
	pre[u] = fa;
	int v;
	for(int i = head[u];~i;i = e[i].nex)
	{
		v = e[i].v;
		if(v == fa)continue;
		qian[v] = i;
		dfs(v,u,deep + 1);
		pos[u] = ++cnt;
		last[cnt] = u;
	}
}
void ST()
{
	for(int i = 1;i <= cnt;i++)
		dp[i][0] = last[i];
	for(int j = 1;(1 << j) <= cnt;j++)
		for(int i = 1;i + (1 << j) - 1 <= cnt;i++)
		{
			dp[i][j] = dp[i][j - 1];
			if(dep[dp[i + (1 << (j - 1))][j - 1]] < dep[dp[i][j - 1]])dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
		}
}
int query(int l,int r)
{
	int k = 0;
	while((1 << (k + 1)) <= r - l + 1)k++;
	if(dep[dp[l][k]] < dep[dp[r - (1 << k) + 1][k]])
		return dp[l][k];
	else return dp[r - (1 << k) + 1][k];
}
void init()
{
	mem(head,-1);
	tole = 0;
	cnt = 0;
}
int find(int x){
	if(x == 1)return 1;
	if(e[qian[x]].c == 1){
		return pre[x] = find(pre[x]);
	}
	else return x;
} 
int x;
void go(int u){
	if(u == 1 || u == x || dep[u] <= dep[x])return;
	c = c / e[qian[u]].c;
	int v;
	if(e[qian[u]].c == 1)
		v = find(u);
	else v = pre[u];
	if(c == 0 || dep[v] <= dep[x] || v == x)return;
	go(v);
}
int main()
{
	while(cin>>n>>m)
	{
		init();
		for(int i = 1,a,b;i <= n - 1;i++){
			scanf("%d%d%I64d",&a,&b,&c);
			make_edge(a,b,c);
			make_edge(b,a,c);
		}
		dfs(1,0,0);
		ST();
		qian[1] = 0;
		while(m--){
			int op;
			int a,b;
			scanf("%d",&op);
			if(op == 1){
				scanf("%d%d",&a,&b);
				cin>>c;
				int aa = a,bb = b;
				if(a == b){
					cout<<c<<"\n";continue;
				}
				a = pos[a],b = pos[b];
				if(a >= b)swap(a,b);
				x = query(a,b);
				go(aa);
				go(bb);
				cout<<c<<"\n";
			}else{
				scanf("%d%I64d",&a,&c);
				e[(a - 1) * 2].c = c;
				e[(a  - 1) * 2 + 1].c = c;
			}
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值