[NOIP2018]保卫王国

[Luogu5024]

感谢包爷的帮助

这一题中可以用\(LCT\)维护虚子树信息 , 即轻儿子信息 ;

\(DP\)方程可以写为从下往上递推的 , 写起来很方便 , 用\(LCT\)维护起来也只是把矩阵乘的顺序交换一下

细节详见代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL INF=1e16+7;
inline LL read(){
    register LL x=0,f=1;register char c=getchar();
    while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
    while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
    return f*x;
}

const int N=1e5+5;
const int M=2e5+5;

struct Edge{
    int v,nxt;
}e[M];
int first[N],Ecnt=0;
inline void Add_edge(int u,int v){
    e[++Ecnt]=(Edge){v,first[u]};
    first[u]=Ecnt;
}

LL a[N],dp[N][2];

struct Matrix{
    LL a[2][2];
    inline Matrix (){a[0][0]=a[0][1]=a[1][0]=a[1][1]=INF;}//初始化
    inline LL* operator [](int x){return a[x];}
    inline friend Matrix operator * (Matrix a,Matrix b){
        Matrix res;
        for(int i=0;i<2;i++){
            for(int j=0;j<2;j++)
                for(int k=0;k<2;k++)
                    res[i][j]=min(res[i][j],a[i][k]+b[k][j]);
        }
        return res;
    }
}g[N],f[N];

namespace LCT{
    int par[N],ch[N][2];
#define ls (ch[x][0])
#define rs (ch[x][1])
    inline bool chk(int x){
        return ch[par[x]][1]==x;
    }
    inline bool nrt(int x){
        return ch[par[x]][0]==x||ch[par[x]][1]==x;
    }
    inline void pushup(int x){
        f[x]=g[x];//定义g[x]为虚边和自己的答案
        if(rs) f[x]=f[rs]*f[x];
        if(ls) f[x]=f[x]*f[ls];
        //虚儿子由于是轻边,顺序一定是f[rs]*g[x]*f[ls],从下往上更新也符合dp思路
    }
    inline void rotate(int x){
        int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
        ch[y][k]=w,par[w]=y;
        if(nrt(y)) ch[z][chk(y)]=x; par[x]=z;
        ch[x][k^1]=y,par[y]=x;
        pushup(y);pushup(x);
    }
    inline void splay(int x){
        while(nrt(x)){
            int y=par[x];
            if(nrt(y)){
                if(chk(x)==chk(y)) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
    }
    inline void access(int x){//只有access的时候才会改变虚子树信息
        for(int y=0;x;x=par[y=x]){
            splay(x);
            if(rs){
                g[x][0][1]+=min(f[rs][1][0],f[rs][1][1]);//rs变为虚边
                g[x][1][1]+=min(f[rs][1][0],f[rs][1][1]);
                g[x][1][0]+=f[rs][1][1];
            }
            if(y){
                g[x][0][1]-=min(f[y][1][0],f[y][1][1]);//y变为实边
                g[x][1][1]-=min(f[y][1][0],f[y][1][1]);
                g[x][1][0]-=f[y][1][1];
            }
            rs=y;
            pushup(x);
        }
    }
    inline void modify(int x,LL val){
        access(x);splay(x);
        g[x][0][1]+=val-a[x];//旋到根然后修改
        g[x][1][1]+=val-a[x];
        pushup(x);
        a[x]=val;// x[a]==a[x]
    }
#undef ls
#undef rs
}using namespace LCT;

inline void dfs(int u,int pre){
    dp[u][1]=a[u];
    par[u]=pre;
    for(int i=first[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==pre) continue;
        dfs(v,u);
        dp[u][1]+=min(dp[v][0],dp[v][1]);
        dp[u][0]+=dp[v][1];
    }
    f[u][0][1]=f[u][1][1]=dp[u][1];
    f[u][1][0]=dp[u][0];
    g[u]=f[u];//一开始全是虚子树
}

signed main(){
    int n=read(),m=read();char op[10];scanf("%s",op);
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n-1;i++){
        int x=read(),y=read();
        Add_edge(x,y);
        Add_edge(y,x);
    }
    dfs(1,0);
    for(int i=1;i<=m;i++){
        int aa=read(),x=read(),bb=read(),y=read();
        int va=a[aa],vb=a[bb];

        if(x) modify(aa,0);//强制选:赋为0才能保证能选到
        else modify(aa,va+INF);//强制不选:这样就选不到
        if(y) modify(bb,0);
        else modify(bb,vb+INF);

        splay(1);//根节点统计答案
        LL ans=min(f[1][1][0],f[1][1][1]);

        if(ans>=INF) puts("-1");
        else{
            if(x) ans+=va;//然后又加上这个点的答案
            if(y) ans+=vb;
            printf("%lld\n",ans);
        }

        modify(aa,va);modify(bb,vb);//又改回去
    }
}

转载于:https://www.cnblogs.com/lizehon/p/10605118.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值