bzoj 4860 (点分治+线段树合并)

bzoj 4860 (点分治+线段树合并)

题意:

给你一颗树,边有颜色对应权值,连续段权值只算一遍,问边数在[l,r]之间的路径中,路径权值的最大值

思路:

树上路径问题,带边数限制。

先考虑点分。求最值,采用点分第二种写法,逐一枚举子树合并

一开始想着对每个颜色都开一颗线段树,查的时候再遍历前面的一遍

显然这个做法是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的。

能否避免再遍历呢?

其实我们发现,我们只关心当前子树是否相同和不相同的两类颜色。可以通过排序当前子树颜色,颜色上形成偏序关系,就具有单调性了

这样我们开两颗线段树,一颗维护和当前子树不同颜色的,一颗维护相同颜色的,边界处之间线段树合并两颗即可,这里有一个问题在于,初始值必须设为INF,因为线段树即使有一个是空的,再pushUp的时候会用0转移更新出错

剩下的就是在每个分治重心模拟计算合并操作了

时间复杂度$O(nlog^2n) $ 空间其实也是2个log的

虽然可以空间回收 ,但在写的时候出了很多锅,发现还要注意一些细节,所以没写
通过排序形成偏序关系,老套路了

#include<bits/stdc++.h>
#define N maxn*36
#define ls lc[p]
#define rs rc[p]
#define lson lc[p],l,mid 
#define rson rc[p],mid+1,r
#define pb push_back 
#define fi first 
#define se second 
using namespace std;
const int maxn=2e5+5;
const int INF=-0x3f3f3f3f;
typedef pair<int,int> P;
vector<P>G[maxn];
int n,m,l,r,val[maxn],mxson[maxn],sz[maxn],rt1,rt2,S,rt,ans=INF,cnttmp;
P tmp[maxn];
bool v[maxn];
struct SegmentTree{
    int lc[N],rc[N],tot,mx[N]={INF};
    void pushUp(int p){
        mx[p]=max(mx[ls],mx[rs]);//?????0 ??????????INF; 
    }
    int merge(int p,int q,int l,int r){
        if(!p||!q)return p+q;
        if(l==r){mx[p]=max(mx[p],mx[q]);return p;}
        int mid=l+r>>1;
        ls=merge(ls,lc[q],l,mid);
        rs=merge(rs,rc[q],mid+1,r);
        pushUp(p);
        return p;
    }
    int query(int p,int l,int r,int L,int R){
        if(!p||L>r||R<l)return INF;
        if(L<=l&&r<=R)return mx[p];
        int mid=l+r>>1;
        int ans=INF;
        if(L<=mid)ans=max(ans,query(lson,L,R));
        if(R>mid)ans=max(ans,query(rson,L,R));
        return ans;
    }
    void update(int&p,int l,int r,int x,int val){
        if(!p){p=++tot;ls=rs=0;mx[p]=INF;}
        if(l==r){
            mx[p]=max(mx[p],val);return;
        }
        int mid=l+r>>1;
        if(x<=mid)update(lson,x,val);
        else update(rson,x,val);
        pushUp(p);
    }
}tr;
void getRoot(int x,int f){
    sz[x]=1;mxson[x]=0;
    for(auto&z:G[x]){
        int y=z.se;
        if(v[y]||y==f)continue;
        getRoot(y,x);
        sz[x]+=sz[y];
        mxson[x]=max(mxson[x],sz[y]);
    }
    mxson[x]=max(mxson[x],S-mxson[x]);
    if(mxson[x]<mxson[rt])rt=x;
}
void init(int x){
    mxson[rt=0]=maxn;
    S=sz[x];
    getRoot(x,0);
}
void getQue(int x,int f,int col,int d,int sum){
	if(d>r)return;
    tmp[++cnttmp]={d,sum};
    if(d>=l&&d<=r) 
    	ans=max(ans,sum);
    for(auto&z:G[x]){
        int y=z.se,edge=z.fi;
        if(y==f||v[y])continue;
        getQue(y,x,edge,d+1,sum+((edge==col)?0:val[edge]));
    }
}
void cal(int x){
    rt1=0;rt2=0;
    int lastcol=0;
    for(int i=0;i<G[x].size();++i){
        int y=G[x][i].se,col=G[x][i].fi;
        if(v[y])continue;
        if(i&&col!=lastcol){
            rt1=tr.merge(rt1,rt2,1,r);rt2=0;
        }
        cnttmp=0;
        getQue(y,x,col,1,val[col]);
        for(int j=1;j<=cnttmp;++j){
            ans=max(ans,tmp[j].se+tr.query(rt1,1,r,max(1,l-tmp[j].fi),r-tmp[j].fi));
			if(rt2)
            	ans=max(ans,tmp[j].se+tr.query(rt2,1,r,max(1,l-tmp[j].fi),r-tmp[j].fi)-val[col]);
        }
        for(int j=1;j<=cnttmp;++j){
                tr.update(rt2,1,r,tmp[j].fi,tmp[j].se);
        }
        lastcol=col;
    }   
}
void dfz(int x){
    v[x]=1;
    cal(x);
    for(auto&z:G[x]){
        int y=z.se;
        if(v[y])continue;
        init(y);
        dfz(rt);
    }
}
int main(){
    scanf("%d%d%d%d",&n,&m,&l,&r);
    int u,v,w;
    for(int i=1;i<=m;++i)scanf("%d",&val[i]);
    for(int i=1;i<n;++i){
        scanf("%d%d%d",&u,&v,&w);
        G[u].pb({w,v});G[v].pb({w,u});
    }
    for(int i=1;i<=n;++i)sort(G[i].begin(),G[i].end());
    S=n;
    mxson[rt=0]=maxn;
    getRoot(1,0);
    dfz(rt);
    cout<<ans<<"\n";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值