Libreoj 2179(点分治+线段树合并

Libreoj 2179(点分治+线段树合并

题意:

给一颗树,边有颜色,连续颜色的边权只出现一次,问所有路径边数在

[L,R]间路径权值的最大值

思路:

限制了路径边数,显然点分治,但是用容斥和枚举子树的方法我都只能口胡出 n 2 l o g n n^2logn n2logn的做法,比如对每个颜色开一颗线段树,下标维护边长,这样每次到一个子树的时候,我们都得枚举前面所有的情况,事实上我们只关心前面的颜色和它是否相同

所以对当前子树按颜色排序形成偏序关系,然后对和当前颜色相同及不同的分别开一颗线段树,当前子树做完就直接在两边查,在颜色交界处暴力线段树合并即可,但是要注意所有线段树初始化必须为INF/在pushUp的时候要特判空树,不然后直接与0取min, WA了100发这里

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

此外这题还有单调队列按秩合并的做法待补,也是点分治的常见套路

#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、付费专栏及课程。

余额充值