解决的问题
对于给出的树上两点求之间的最值或者更新操作变为logn。
其他方法
Tarjan求LCA的复杂度为 O(N+Q)所以不断更新复杂度太高。
本质: 就是将树划分为不重合的多条链每条链都有一个线段树中的编号(可类比dfs序转换线段树的想法)+线段树。在求的过程中根据重链不断逼近再用线段树维护即可。
一般有对点建树和对边建树两种方案,看题目要求。对第几条边进行操作这种情况要将边存储下来。
其实就是一个点的影响范围。线段树的话更新要更新到单点。
模板
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 0x3f3f3f3f
#define maxn (100+100000)
using namespace std;
typedef long long ll;
struct Edge{
int u,to,next;
}edge[maxn*2];
int head[maxn],tot;
int top[maxn];///top[v] 表示以 v所在的重链顶端节点
int fa[maxn]; /// 父亲结点
int deep[maxn];/// 深度
int num[maxn];/// num[v] 表示以v为根的子树节点数
int p[maxn];/// p[v] 表示v与其父亲结点的连边在线段树中的位置 PS:这个应该是对边进行剖分
int fp[maxn];///和p相反
int son[maxn];///重儿子
int pos;///线段树总数
void init(){
tot = 0;
memset(head,-1,sizeof(head));
pos = 0;///
memset(son,-1,sizeof(son));
}
void addedge(int u ,int v ){
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs1(int u,int pre,int d){ ///第一遍求出fa,deep,num,son
deep[u] = d;
fa[u] = pre;
num[u] = 1;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].to;
if(v==pre) continue;
dfs1(v,u,d+1);
num[u] += num[v];
if(son[u]==-1 || num[v] > num[son[u]])
son[u] = v;
}
}
///这里没有传入pre这个参数而是用fa ,是因为fa在后面有用,这样也无妨
void dfs2(int u,int sp){ /// 第二遍求出top,p,fp
top[u] = sp;
p[u] = pos++;
fp[p[u]] = u;
if(son[u]==-1)
return;
dfs2(son[u],sp);
for(int i = head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(v != son[u] && v!= fa[u])
dfs2(v,v);
}
}
struct Node{
int l,r;
ll Max;
}segTree[maxn*3];
void push_up(int i) {
segTree[i].Max = segTree[i<<1].Max + segTree[(i<<1)|1].Max;
///segTree[i].Max = min(segTree[i<<1].Max , segTree[(i<<1)|1].Max);
}
void build(int i,int l,int r)
{
segTree[i].l = l;
segTree[i].r = r;
segTree[i].Max = 0;
if(l == r)return;
int mid = (l+r)/2;
build(i<<1,l,mid);
build((i<<1)|1,mid+1,r);
}
void update(int i,int k,int val) { /// 更新线段树的第k个值为val
if(segTree[i].l == k && segTree[i].r == k) {
segTree[i].Max = val;
return;
}
int mid = (segTree[i].l + segTree[i].r)/2;
if(k <= mid)update(i<<1,k,val);
else update((i<<1)|1,k,val);
push_up(i);
}
ll query(int i,int l,int r) { ///查询线段树中[l,r] 的最大值
if(segTree[i].l >= l && segTree[i].r <= r) /// 这个写法和平常不一样是因为查询区间必然是重链上的点
return segTree[i].Max;
int mid = (segTree[i].l + segTree[i].r)/2;
ll ans = 0;
if(r <= mid) ans = query(i<<1,l,r);
else if(l > mid) ans = query((i<<1)|1,l,r);
else {
ans = query(i<<1,l,mid);
ans += query((i<<1)|1,mid+1,r);
}
return ans;
}
ll find(int u,int v){ /// 查询u->v边的最大值
int f1 = top[u],f2 = top[v];
ll tmp = 0;
while(f1 != f2){
if(deep[f1] < deep[f2])
swap(f1,f2),swap(u,v);
tmp = tmp + query(1,p[f1],p[u]);
u = fa[f1]; f1 = top[u];
}
if(u==v) return tmp;///当以点剖分的时候不要这句因为点要算入,边的话就不算入。这里是用边
if(deep[u] > deep[v] ) swap(u,v);
return tmp + query(1,p[son[u]],p[v]); ///这个只到u所以用它孩子的连线
}
int e[maxn][3];
int main(){
int T,cas=1;
int n,m;
while(~scanf("%d%d",&n,&m)){
init();
for(int i = 1;i<n;i++){
scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);
addedge(e[i][0],e[i][1]);
addedge(e[i][1],e[i][0]);
}
dfs1(1,0,0);
dfs2(1,1);
build(1,0,pos-1);
for(int i = 1;i<n;i++){
if(deep[e[i][0]] > deep[e[i][1]])
swap(e[i][0],e[i][1]);
update(1,p[e[i][1]],e[i][2]); ///刚开始第三个参数写成这个p[e[i][2]]错了,这个这样写是因为存放边值
}
for(int i = 0;i<m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a) printf("%I64d\n",find(b,c));
else update(1,p[e[b][1]],c);///更新边还是点 点的话就是p[u] 第几条边就是 e[u][1]
}//这句写错了wa好久
}
return 0;
}
时间: 2024-10-15 20:21:26