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\n",ans);
    }
}

posted @ 2020-11-10 23:02  yzhx  阅读(81)  评论(0编辑  收藏  举报