zoukankan      html  css  js  c++  java
  • NOIP2018--保卫王国

    题面简化

    给定大小为n的树,树上每个点被选中需要代价,每条边连接的两端至少要有一个被选中
    m组询问,钦定两个点的状态,询问最小花费值
    洛谷

    题解

    先考虑暴力的复杂度瓶颈:

    O(n^2), 是因为树形DP中有两个点的状态被钦定了, 导致我们不得不重新计算整棵树,

    重复计算的内容:

    最初的树形DP中,每个点的贡献被累加到父亲上
    我们可以发现钦定点的兄弟的DP贡献是不会受影响的
    以此类推,只有两个钦定点的路径上的DP值受到影响

    消除重复计算

    知道了受到影响的部分之后,很自然的可以想到对于每个点记录其父亲子树除去当前子树的DP
    这样显然是可以一路推上去的
    然而当树高很高时是会被卡的,根据lca的求法,可以试着用倍增
    即:
    (dp[i][j][p(0/1)][q(0/1)])
    表示i的(2^j)的祖先状态为p,i状态为q时
    以i的(2^j)的祖先为根的子树除去以i为根的子树的贡献
    设: g[i][0/1]记录一下整棵树除去以i为根的子树的dp值
    然后: 求解钦定的两点的lca,倍增处理路径的改变,lca更高的部分直接用g数组

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    #define ll long long
    #define int ll
    #define get getchar()
    #define in inline
    in int read()
    {
        int t=0; char ch=get;
        while(ch<'0' || ch>'9') ch=get;
        while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
        return t;
    }
    const int _=3e5+3;
    const int inf=0x3f3f3f3f3f3f3f3f;
    struct edge{
        int to,ne;
    }e[_<<2];
    int val[_],dep[_],h[_],q,n,tot,fa[_][25];
    int dp[_][25][2][2],f[_][2],g[_][2];
    in void add(int x,int y)
    {    e[++tot].ne=h[x], e[tot].to=y, h[x]=tot;}
    char type[20];
    #define mid fa[u][i-1]
    in void dfs(int u,int father)
    {
        fa[u][0]=father, dep[u]=dep[father]+1;
        for(re int i=1;i<=23;i++) fa[u][i]=fa[mid][i-1];
        f[u][1]=val[u];
        for(re int i=h[u];i;i=e[i].ne) {
            int v=e[i].to;
            if(v==father) continue;
            dfs(v,u);
            f[u][1]+=min(f[v][1],f[v][0]);
            f[u][0]+=f[v][1];
        }
    }
    in void Pre(int u)
    {
        int father=fa[u][0];
        dp[u][0][0][0]=inf;
        dp[u][0][1][0]=f[father][0]-f[u][1];
        dp[u][0][1][1]=dp[u][0][0][1]=f[father][1]-min(f[u][0],f[u][1]);
        for(re int i=1;i<=23;i++) {
            dp[u][i][0][0]=min(dp[mid][i-1][1][0]+dp[u][i-1][0][1],
                               dp[mid][i-1][0][0]+dp[u][i-1][0][0]);
            dp[u][i][1][0]=min(dp[mid][i-1][1][0]+dp[u][i-1][1][1],
                               dp[mid][i-1][0][0]+dp[u][i-1][1][0]);
            dp[u][i][0][1]=min(dp[mid][i-1][1][1]+dp[u][i-1][0][1],
                               dp[mid][i-1][0][1]+dp[u][i-1][0][0]);
            dp[u][i][1][1]=min(dp[mid][i-1][1][1]+dp[u][i-1][1][1],
                               dp[mid][i-1][0][1]+dp[u][i-1][1][0]);
        }
        for(re int i=h[u];i;i=e[i].ne) {
            int v=e[i].to;
            if(v==father) continue;
            g[v][0]=g[u][1]+f[u][1]-min(f[v][1],f[v][0]);
            g[v][1]=min(g[v][0],g[u][0]+f[u][0]-f[v][1]);
            Pre(v);
        }
    }
    in int calc(int u,int o1,int v,int o2) //具体计算每个询问
    {
        if(dep[u]<dep[v]) swap(u,v), swap(o1,o2);
        int s0=f[u][0],s1=f[u][1]; //分别表示当前点(选或不选)的子树贡献
        if(o1) s0=inf;
        else s1=inf;
        for(re int i=23;i>=0;i--) //先把u跳到和v同一深度
            if(dep[fa[u][i]]>=dep[v]) {
                int t0=s0,t1=s1;
                s0=min(t0+dp[u][i][0][0],t1+dp[u][i][1][0]); //之前在dp数组中已经减掉了当前点为根的原贡献
                s1=min(t0+dp[u][i][0][1],t1+dp[u][i][1][1]);//现在加上新贡献
                u=fa[u][i];
            }
        if(u==v) return g[v][o2]+ (o2 ? s1 : s0);
        int w0=f[v][0],w1=f[v][1];
        if(o2) w0=inf;
        else w1=inf;
        for(re int i=23;i>=0;i--) //同时跳
            if(fa[u][i]!=fa[v][i]) {
                int t0=s0,t1=s1;
                s0=min(t0+dp[u][i][0][0],t1+dp[u][i][1][0]);
                s1=min(t0+dp[u][i][0][1],t1+dp[u][i][1][1]);
                u=fa[u][i];
                t0=w0,t1=w1;
                w0=min(t0+dp[v][i][0][0],t1+dp[v][i][1][0]);
                w1=min(t0+dp[v][i][0][1],t1+dp[v][i][1][1]);
                v=fa[v][i];
            }
        int lca=fa[u][0];
        return min(f[lca][0]-f[u][1]-f[v][1]+g[lca][0]+s1+w1, //lca不选
                   f[lca][1]-min(f[u][1],f[u][0])-min(f[v][1],f[v][0])+g[lca][1]+min(s1,s0)+min(w1,w0));//lca选
    }
    in int check(int u,int v,int x,int y)
    {
        if(x|y) return 0;
        if(dep[u]<dep[v]) swap(u,v),swap(x,y);
        if(fa[u][0]==v) return 1;
        return 0;
    }
    signed main()
    {
        memset(dp,0x3f,sizeof(dp));
        memset(g,0x3f,sizeof(g));
        n=read();scanf("%lld%s",&q,type+1);
        for(re int i=1;i<=n;i++) val[i]=read();
        for(re int i=1;i<n;i++) {
            int x=read(), y=read();
            add(x,y), add(y,x);
        }
        dfs(1,0);
        g[1][0]=g[1][1]=0;
        Pre(1);
        for(re int i=1;i<=q;i++) {
            int u=read(), x=read(), v=read(), y=read();
            if(check(u,v,x,y)) {puts("-1");continue;}
            int ans=calc(u,x,v,y);
            printf("%lld
    ",ans);
        }
    }
    
    
  • 相关阅读:
    CF 444B(DZY Loves FFT-时间复杂度)
    摆弄【Nhibernate 协会制图--导乐陪伴分娩】
    固定的报文统计报告的规定
    CSS——(2)与标准流盒模型
    自动复制转换StringBuffer
    IM信息网
    Oracle Redo Log
    【转载】有哪些省时小技巧,是每个Linux用户都应该知道的
    Linux snmp
    MySQL zabbix
  • 原文地址:https://www.cnblogs.com/yzhx/p/13956562.html
Copyright © 2011-2022 走看看