2016 Multi-University Training Contest 4 1007 (hdu 5770)

题目链接

题目大意

给定一棵树,树上有m个宝箱,每个宝箱都有一个把钥匙,以及各自的价值,要先拿到钥匙才可以开宝箱,必须开掉所有能开的宝箱。要求选择一条简单路径,使得最后获得的总价值最大。

题解

一开始各种想树上的算法,树分治什么的。
比赛后一看题解,居然是扫描线+线段树
简直是到神(keng)题。
好吧这么考虑,首先利用dfs序,把树转到序列上。
然后对于每种情况,可以进行分类讨论。
令lca=LCA(A,B),假定A是宝箱所在的结点,B是钥匙所在的结点。
用一个点对(a,b)表示起点在dfn为a的点,终点在dfn为b的点。

1.A!=B&&A,B!=lca

如果起点在A的子树中,终点在B的子树中,那么取到这个宝箱.

2.A==B

由于不可能找出所有的经过A的点(那是 O(n2) 级的)。
正难则反。
先把这个宝箱的权值加到答案中去,然后在对于不经过这个点的路径减去这个点的权值。
这是又要分出三类

1

dfs序小于A的点连到dfs序大于A的点。

2

dfs序小于A点的连向dfs序小于A的点。
dfs序大于A点的连向dfs序大于A的点。

3

枚举所有A点的子树,然后子树内部相连。
这就是 O(n) 级别的了。
当然这样貌似会有极限数据,不过出题人不卡。

3.lca=A&&A!=B

找到一个点,离A最近,记为C,那么起点在B的子树中,终点不在D的子树中的所有路径都可以拿到这个宝箱。
到这里似乎和扫描线没什么关系。
由于dfs在表示子树是都是连续的,那么就可以把以上这些情况用一些带权矩阵表示出来。
再把带权矩阵拆成两条线段。
扫描线+线段树就好了。

if(a==b){
    ans+=c;
    A[1]=1,B[1]=L[a]-1;
    A[2]=R[a]+1,B[2]=n;
    rep(j,0,edge[a].size()){
        int to=edge[a][j];
        if(to==pre[0][a]) continue;
        add_matrix(L[to],R[to],L[to],R[to],-c);
    }
    rep(k,1,3)rep(j,1,3)
        add_matrix(A[k],B[k],A[j],B[j],-c);
}else if(a==lca){
    pointd=up(b,dep[b]-dep[a]-1);
    add_matrix(L[b],R[b],1,L[pointd]-1,c);
    add_matrix(L[b],R[b],R[pointd]+1,n,c);
}else if(b==lca){
    pointd=up(a,dep[a]-dep[b]-1);
    add_matrix(1,L[pointd]-1,L[a],R[a],c);
    add_matrix(R[pointd]+1,n,L[a],R[a],c);
}else add_matrix(L[b],R[b],L[a],R[a],c);
vector<int>edge[M];
int dep[M],L[M],dfs_clock,pre[S][M],R[M];
int mx[M<<2],add[M<<2],pointd;
void build(int L,int R,int p){
    add[p]=mx[p]=0;
    if(L==R) return;
    int mid=(L+R)>>1;
    build(L,mid,p<<1);
    build(mid+1,R,p<<1|1);
}
inline void down(int p){
    if(!add[p]) return;
    int l=p<<1,r=p<<1|1;
    add[l]+=add[p];
    add[r]+=add[p];
    mx[l]+=add[p];
    mx[r]+=add[p];
    add[p]=0;
}
inline void up(int p){
    mx[p]=max(mx[p<<1],mx[p<<1|1]);
}
void update(int L,int R,int w,int p,int l,int r){
    if(L==l&&r==R){
        add[p]+=w;
        mx[p]+=w;
        return;
    }
    down(p);
    int mid=(l+r)>>1;
    if(mid>=R) update(L,R,w,p<<1,l,mid);
    else if(mid<L) update(L,R,w,p<<1|1,mid+1,r);
    else update(L,mid,w,p<<1,l,mid),update(mid+1,R,w,p<<1|1,mid+1,r);
    up(p);
}
void dfs(int x,int f){
    L[x]=++dfs_clock;
    dep[x]=dep[f]+1;
    pre[0][x]=f;
    rep(i,0,edge[x].size()){
        int to=edge[x][i];
        if(to==f) continue;
        dfs(to,x);
    }
    R[x]=dfs_clock;
}
int up(int a,int step){
    if(step<0) return a;
    rep(i,0,S)if(step&(1<<i)) a=pre[i][a];
    return a;
}
int LCA(int a,int b){
    if(dep[a]<dep[b]) swap(a,b);
    a=up(a,dep[a]-dep[b]);
    if(a!=b){
        per(i,0,S)if(pre[i][a]!=pre[i][b]) a=pre[i][a],b=pre[i][b];
        a=pre[0][a];
    }
    return a;
}
struct Liner{
    int x,a,b,c;
    Liner(int _x=0,int _a=0,int _b=0,int _c=0):x(_x),a(_a),b(_b),c(_c){}
    bool operator <(const Liner&tmp)const{
        return x<tmp.x;
    }
}lin[M<<5];
int ltot;
inline void add_matrix(int a,int b,int c,int d,int e){
    if(a>b||c>d) return;
    lin[ltot++]=Liner(a,c,d,e);
    lin[ltot++]=Liner(b+1,c,d,-e);
}
int A[4],B[4];
void solve(int cas){
    rd(n);rd(m);
    ltot=dfs_clock=0;
    rep(i,1,n+1) edge[i].clear();
    rep(i,1,n){
        int a,b;
        rd(a);rd(b);
        edge[a].push_back(b);
        edge[b].push_back(a);
    }
    dfs(1,0);
    rep(j,1,S)rep(i,1,n+1)pre[j][i]=pre[j-1][pre[j-1][i]];
    int ans=0;
    rep(i,0,m){
        int a,b,c;
        rd(b);rd(a);
        scanf("%d",&c);
        int lca=LCA(a,b);
        if(a==b){
            ans+=c;
            A[1]=1,B[1]=L[a]-1;
            A[2]=R[a]+1,B[2]=n;
            rep(j,0,edge[a].size()){
                int to=edge[a][j];
                if(to==pre[0][a]) continue;
                add_matrix(L[to],R[to],L[to],R[to],-c);
            }
            rep(k,1,3)rep(j,1,3)
                add_matrix(A[k],B[k],A[j],B[j],-c);
        }else if(a==lca){
            pointd=up(b,dep[b]-dep[a]-1);
            add_matrix(L[b],R[b],1,L[pointd]-1,c);
            add_matrix(L[b],R[b],R[pointd]+1,n,c);
        }else if(b==lca){
            pointd=up(a,dep[a]-dep[b]-1);
            add_matrix(1,L[pointd]-1,L[a],R[a],c);
            add_matrix(R[pointd]+1,n,L[a],R[a],c);
        }else add_matrix(L[b],R[b],L[a],R[a],c);
    }
    sort(lin,lin+ltot);
    build(1,n,1);
    int res=-INF;
    rep(i,0,ltot){
        int R=i;
        while(R+1<ltot&&lin[R+1].x==lin[i].x) R++;
        rep(j,i,R+1) update(lin[j].a,lin[j].b,lin[j].c,1,1,n);
        down(1);
        res=max(res,mx[1]);
        i=R;
    }
    printf("Case #%d: %d\n",cas,ans+res);
}
int main(){
    int T;
    rd(T);
    for(int i=1;i<=T;i++) solve(i);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值