题目
思路壹
首先我要告诉你,这可以变成一个树剖的板子题,因为
⌊ ⌊ n a ⌋ b ⌋ = ⌊ n a b ⌋ \left\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\right\rfloor=\left\lfloor\frac{n}{ab}\right\rfloor ⌊b⌊an⌋⌋=⌊abn⌋
这是容易说明的。考虑左式,其数学意义为,找一最大整数 x x x 使得 b x ≤ ⌊ n a ⌋ bx\le\lfloor\frac{n}{a}\rfloor bx≤⌊an⌋ ,显然等价于 b x ≤ n a bx\le \frac{n}{a} bx≤an ,自然也等价于 a b x ≤ n abx\le n abx≤n ,与右式的数学意义相同。
然后我们只需要维护树上路径的乘积。复杂度 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;
}