[CF593D]Happy Tree Party

122 篇文章 0 订阅
98 篇文章 0 订阅

题目

传送门 to luogu

思路壹

首先我要告诉你,这可以变成一个树剖的板子题,因为

⌊ ⌊ n a ⌋ b ⌋ = ⌊ n a b ⌋ \left\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\right\rfloor=\left\lfloor\frac{n}{ab}\right\rfloor ban=abn

这是容易说明的。考虑左式,其数学意义为,找一最大整数 x x x 使得 b x ≤ ⌊ n a ⌋ bx\le\lfloor\frac{n}{a}\rfloor bxan ,显然等价于 b x ≤ n a bx\le \frac{n}{a} bxan ,自然也等价于 a b x ≤ n abx\le n abxn ,与右式的数学意义相同。

然后我们只需要维护树上路径的乘积。复杂度 O ( m log ⁡ 2 n + n log ⁡ n ) \mathcal O(m\log^2n+n\log n) O(mlog2n+nlogn)

代码壹

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 200005;
int n;

struct Edge{
	int to, nxt; int_ val;
	Edge(){ } // nothing
	Edge(int T,int N,int_ V){
		to = T, nxt = N, val = V;
	}
} e[MaxN<<1|1];
int head[MaxN], cntEdge;
void addEdge(int a,int b,int_ c){
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],c);
	head[b] = cntEdge ++;
}

int siz[MaxN], son[MaxN], fa[MaxN];
void dfs(int x){
	siz[x] = 1;
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != fa[x]){
			fa[e[i].to] = x, dfs(e[i].to);
			siz[x] += siz[e[i].to];
			if(siz[e[i].to] > siz[son[x]])
				son[x] = e[i].to;
		}
}

void modify(int,int_,int,int);
int top[MaxN], dfn[MaxN], dfsClock;
void build(int x,int tc){
	top[x] = tc, dfn[x] = ++ dfsClock;
	if(son[x]) build(son[x],tc);
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != fa[x]){
			if(e[i].to != son[x])
				build(e[i].to,e[i].to);
			modify(dfn[e[i].to],e[i].val,1,n);
		}
}

const int_ __M = 1ll<<60;
int_ mul(int_ a,int_ b){
	return __M/a < b ? __M : a*b;
}

int_ v[MaxN<<1]; // 线段树节点
int __id(int l,int r){ return (l+r)|(l!=r); }
void modify(int qid,int_ adv,int l=1,int r=n){
	if(l == r){
		v[l<<1] = adv; return ;
	}
	int m = (l+r)>>1;
	if(qid <= m) modify(qid,adv,l,m);
	else modify(qid,adv,m+1,r);
	v[m<<1|1] = mul(v[__id(l,m)],v[__id(m+1,r)]);
}
int_ query(int ql,int qr,int l,int r=n){
	if(ql <= l && r <= qr)
		return v[__id(l,r)];
	int_ res = 1; int m = (l+r)>>1;
	if(ql <= m)
		res = mul(res,query(ql,qr,l,m));
	if(m < qr)
		res = mul(res,query(ql,qr,m+1,r));
	return res;
}

int_ query(int a,int b){
	int_ res = 1;
	while(top[a] != top[b]){
		if(dfn[top[a]] < dfn[top[b]]) swap(a,b);
		res = mul(res,query(dfn[top[a]],dfn[a],1));
		a = fa[top[a]];
	}
	if(a == b) return res;
	if(dfn[a] < dfn[b]) swap(a,b);
	return mul(res,query(dfn[b]+1,dfn[a],1));
}

int main(){
	n = readint(); int m = readint();
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	for(int i=2; i<=n*2; ++i)
		v[i] = 1; // 防止 mul 的时候 RE
	for(int i=1,a,b; i<n; ++i){
		a = readint(), b = readint();
		addEdge(a,b,readint());
	}
	dfs(1), build(1,1);
	while(m --){
		if(readint() == 2){
			int a = readint()-1;
			int b = e[a<<1].to;
			a = e[a<<1|1].to;
			if(dfn[a] < dfn[b])
				swap(a,b);
			modify(dfn[a],readint());
			continue;
		}
		int_ dym = query(readint(),readint());
		printf("%lld\n",readint()/dym);
	}
	return 0;
}

思路贰

有另一个很神奇的想法。作除法的下降速度极快,是 O ( log ⁡ v ) \mathcal O(\log v) O(logv) 级别的。唯一麻烦的是权值为 1 1 1 的边。

嘿,现在想想,题目中的修改只会让权值变小,也就是说,变成 1 1 1 就不会变回去。于是用并查集快速查找第一条不为 1 1 1 的边。

复杂度 O ( n log ⁡ n + m log ⁡ v ) \mathcal O(n\log n+m\log v) O(nlogn+mlogv) ,跑起来比树剖快一些。注意:树上并查集不可以用按秩合并,所以复杂度是最坏 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的。

代码贰

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 200005;

struct Edge{
	int to, nxt; int_ val;
	Edge(){ } // nothing
	Edge(int T,int N,int_ V){
		to = T, nxt = N, val = V;
	}
} e[MaxN<<1|1];
int head[MaxN], cntEdge;
void addEdge(int a,int b,int_ c){
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],c);
	head[b] = cntEdge ++;
}

namespace UFS{
	int fa[MaxN];
	void init(int n){
		for(int i=1; i<=n; ++i)
			fa[i] = i;
	}
	inline int find(int a){
		if(a != fa[a])
			fa[a] = find(fa[a]);
		return fa[a];
	}
	void combine(int a,int b){
		fa[find(a)] = find(b);
	}
}

int dep[MaxN], fa[MaxN];
int_ fae[MaxN]; // 其实也可以改成 depth
void build(int x){
	dep[x] = dep[fa[x]]+1;
	for(int i=head[x]; ~i; i=e[i].nxt){
		if(e[i].to == fa[x]) continue;
		fa[e[i].to] = x;
		fae[e[i].to] = e[i].val;
		build(e[i].to);
		if(e[i].val == 1)
			UFS::combine(e[i].to,x);
	}
}

int_ query(int a,int b,int_ c){
	a = UFS::find(a), b = UFS::find(b);
	while(a != b){
		if(dep[a] < dep[b]) swap(a,b);
		c /= fae[a], a = UFS::find(fa[a]);
		if(c == 0) break; // 重要!
	}
	return c;
}

int main(){
	int n = readint(), m = readint();
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	UFS::init(n);
	for(int i=1,a,b; i<n; ++i){
		a = readint(), b = readint();
		addEdge(a,b,readint());
	}
	build(1);
	while(m --){
		if(readint() == 2){
			int a = readint()-1;
			int b = e[a<<1].to;
			a = e[a<<1|1].to;
			if(dep[a] < dep[b])
				swap(a,b);
			fae[a] = readint();
			if(fae[a] == 1)
				UFS::combine(a,b);
			continue;
		}
		int a = readint(), b = readint();
		printf("%lld\n",query(a,b,readint()));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值