【NOIP 2018】Day2 T3 保卫王国

Author Avatar
洛水·锦依卫 1月 03, 2019

【NOIP 2018】Day2 T3 保卫王国

题目描述

Z 国有$n$座城市,$n - 1$条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。

Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:

  • 一座城市可以驻扎一支军队,也可以不驻扎军队。
  • 由道路直接连接的两座城市中至少要有一座城市驻扎军队。
  • 在城市里驻扎军队会产生花费,在编号为$i$的城市中驻扎军队的花费是$p_i$。

小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出 了$m$个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一 给出回答。具体而言,如果国王提出的第$j$个要求能够满足上述驻扎条件(不需要考虑 第 $j$ 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果 国王提出的第j个要求无法满足,则需要输出$-1~~(1 ≤ j ≤ m)$。现在请你来帮助小 Z。

输入输出格式

输入格式:

第 1 行包含两个正整数$n,m$和一个字符串$type$,分别表示城市数、要求数和数据类型。$type$是一个由大写字母 $A$,$B$ 或 $C$ 和一个数字 1,2,3 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。这个参数的含义在【数据规模与约定】中 有具体的描述。

第 2 行$n$个整数$p_i$,表示编号$i$的城市中驻扎军队的花费。

接下来 $n - 1$ 行,每行两个正整数$u,v$,表示有一条$u$到$v$的双向道路。

接下来 $m$ 行,第$j$行四个整数$a,x,b,y(a ≠ b)$,表示第$j$个要求是在城市$a$驻扎$x$支军队, 在城市$b$驻扎$y$支军队。其中,$x$ , $y$的取值只有 0 或 1:若 $x$ 为 0,表示城市 $a$ 不得驻 扎军队,若 $x$ 为 1,表示城市 $a$ 必须驻扎军队;若 $y$为 0,表示城市 $b$不得驻扎军队, 若 $y$为 1,表示城市 $b$ 必须驻扎军队。

输入文件中每一行相邻的两个数据之间均用一个空格分隔。

输出格式:

输出共 $m$ 行,每行包含 1 个整数,第$j$行表示在满足国王第$j$个要求时的最小开销, 如果无法满足国王的第$j$个要求,则该行输出 -1。

输入输出样例

输入样例#1:

5 3 C3 
2 4 1 3 9 
1 5 
5 2 
5 3 
3 4 
1 0 3 0 
2 1 3 1 
1 0 5 0

输出样例#1:

12 
7 
-1

说明

【样例解释】

对于第一个要求,在 4 号和 5 号城市驻扎军队时开销最小。

对于第二个要求,在 1 号、2 号、3 号城市驻扎军队时开销最小。

第三个要求是无法满足的,因为在 1 号、5 号城市都不驻扎军队就意味着由道路直接连 接的两座城市中都没有驻扎军队。

【数据规模与约定】

对于 $100\%$的数据,$n,m ≤ 100000,1 ≤ p_i ≤ 100000$。

数据类型的含义:

A:城市$i$与城市$i + 1$直接相连。
B:任意城市与城市 1 的距离不超过 100(距离定义为最短路径上边的数量),即如果这 棵树以 1 号城市为根,深度不超过 100。
C:在树的形态上无特殊约束。
1:询问时保证$a = 1,x = 1$,即要求在城市 1 驻军。对b,y没有限制。
2:询问时保证$a,b$是相邻的(由一条道路直接连通)
3:在询问上无特殊约束。

考点

倍增,树形DP

思路

首先看到数据范围为 $1e5$,那么发现 $n^2$ 算法是不可能过的,不难想到算法是 $nlogn$ 的。那么接着看到题面,我们会发现一件事,那就是每个询问只影响两个点,而通过树形$dp$的过程我们可以知道一件事情:除了一个询问影响的两个点以外的点的$dp$过程其实都是没有变化的,或者说,除去这两个点,当做整棵树里没有它们以及它们的子树,只考虑其他点的最优 $dp$ 值还是不变!

那么不难想到,我们处理出除了这两个点以外的 $dp$ 值,再单独处理这两个点不就行了吗?可是不能直接处理,时间空间都会爆炸。那么你想到了什么吗?倍增!联赛每年必考的倍增不见了!那么题目不就清晰明朗起来了吗?

$f[i][0/1]$表示选或不选$i$结点,选择以$i$为根的子树所有结点的最优值。$g[i][0/1]$表示的是选或不选$i$结点,整棵树除了以$i$为根的子树以外全部选择的最优值。然后呢?当然就是倍增!设$fa$为$i$的$2^j$祖先,$dp[i][j][0/1][0/1]$表示的是$i$结点选或不选,$i$的$2^j$祖先$fa$选或不选,以$fa$为根的子树内独不选以$i$为根的子树的最优值!换句话来说,也可以说是 $i$ 结点相对于它的 $2^j$ 祖先结点为根的树的另一个 $g$ 数组。

那么处理出了倍增数组之后呢?我们这样做:对于两个询问的点不断向上跳并统计$dp$值,如果其中一个是另一个的祖先结点,那么这一个的答案就可以直接输出。否则继续向上统计答案,直到到达$lca$处。为什么跳到这种位置就行了呢?因为修改两点的影响范围是有限的,对于范围之外的点它们的$dp$值并不会受到影响!

具体怎么跳?对于当前点$x$来说,记录它的子树最优值$tx[1/0]$,代表当前点选或不选的子树最优值。那么对于初始修改点,$tx$数组的两个数里只会有一个是有值的,另一个设为$inf$即可。另一个点$y$自然同理。

至于判断 $-1$?$emm…$如果修改的两点是相连的并且修改值都为 $0$ 呗……

代码

这个地方我分开写伐。

f 数组:

void DP(int x)
{
    f[x][1]=w[x];
    for(int i=head[x];i;i=nx[i])
        if(to[i]!=fa[x])
        {
            int p=to[i];
            DP(p);
            f[x][1]+=min(f[p][0],f[p][1]);
            f[x][0]+=f[p][1];
        }
}

g 数组:

void D_P(int x)
{
    for(int i=head[x];i;i=nx[i])
        if(to[i]!=fa[x])
        {
            int p=to[i];
            g[p][0]=g[x][1]+f[x][1]-min(f[p][0],f[p][1]);
            g[p][1]=min(g[x][0]+f[x][0]-f[p][1],g[p][0]);
            D_P(p);
        }
}

重点- dp 数组:

for(int i=1;i<=n;i++)
{
    jump[i][0]=fa[i];
    dp[i][0][0][0]=2e15;
    dp[i][0][0][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);//这里你可能会疑惑:为什么是减去min?因为这并不是别的,而是减去f数组DP上来的时候贡献的DP值
    dp[i][0][1][0]=f[fa[i]][0]-f[i][1];
    dp[i][0][1][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
}
for(int j=1;j<=17;j++)
    for(int i=1;i<=n;i++)
    {
        jump[i][j]=jump[jump[i][j-1]][j-1];
        for(int k=0;k<=1;k++)
            for(int l=0;l<=1;l++)
            {
                dp[i][j][k][l]=2e15;
                for(int q=0;q<=1;q++)
                    dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][q]+dp[jump[i][j-1]][j-1][q][l]);
            }
    }

关键-向上跳:

long long work()
{
    先跳到同一深度
    if(deep[a]<deep[b])
    {
        swap(a,b);
        swap(c,d);
    }
    tx,ty分别代表a,b跳到当前点的最优值,nx,ny代表下一步转移的值
    long long tx[2]={inf,inf},ty[2]={inf,inf},nx[2],ny[2];
    tx[c]=f[a][c],ty[d]=f[b][d];
    for(int i=17;i>=0;i--)
        if(deep[jump[a][i]]>=deep[b])
        {
            nx[0]=nx[1]=inf;
            for(int j=0;j<=1;j++)
                for(int k=0;k<=1;k++)
                    nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
            tx[0]=nx[0],tx[1]=nx[1];
            a=jump[a][i];
        }
    if(a==b)
        return tx[d]+g[b][d];//如果b为a的祖先,那么直接返回DP最优值+子树外最优值
    a,b一起跳
    for(int i=17;i>=0;i--)
        if(jump[a][i]!=jump[b][i])
        {
            nx[0]=nx[1]=inf;
            ny[0]=ny[1]=inf;
            for(int j=0;j<=1;j++)
                for(int k=0;k<=1;k++)
                {
                    nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
                    ny[j]=min(ny[j],ty[k]+dp[b][i][k][j]);
                }
            tx[0]=nx[0],tx[1]=nx[1];
            ty[0]=ny[0],ty[1]=ny[1];
            a=jump[a][i];
            b=jump[b][i];
        }
    int lca=fa[a];
    判断返回值哪个更优
    return min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0],f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1])+g[lca][1]);
    展开写法:
    long long sum1=min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0]);//减去原本DP值并加上跳过来的最优值再加上子树外最优值。
    long long sum2=min(f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1]))+g[lca][1];//减去原本DP值加上当前最优加上子树外最优
}

总代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,nx[200001],to[200001],head[100001],a,b,c,d;
int fa[200001],deep[200001],w[100001],jump[100001][18];
long long f[100001][2],g[100001][2],dp[100001][18][2][2],inf=2e15;
char type[2];
void add(int u,int v,int d)
{
    nx[d]=head[u];
    to[d]=v;
    head[u]=d;
}
void build(int x)
{
    for(int i=head[x];i;i=nx[i])
        if(to[i]!=fa[x])
        {
            fa[to[i]]=x;
            deep[to[i]]=deep[x]+1;
            build(to[i]);
        }
}
void DP(int x)
{
    f[x][1]=w[x];
    if(x==a)
        f[x][(c+1)%2]=inf;
    if(x==b)
        f[x][(d+1)%2]=inf;
    for(int i=head[x];i;i=nx[i])
        if(to[i]!=fa[x])
        {
            int p=to[i];
            DP(p);
            f[x][1]+=min(f[p][0],f[p][1]);
            f[x][0]+=f[p][1];
        }
}
void D_P(int x)
{
    for(int i=head[x];i;i=nx[i])
        if(to[i]!=fa[x])
        {
            int p=to[i];
            g[p][0]=g[x][1]+f[x][1]-min(f[p][0],f[p][1]);
            g[p][1]=min(g[x][0]+f[x][0]-f[p][1],g[p][0]);
            D_P(p);
        }
}
long long work()
{
    if(deep[a]<deep[b])
    {
        swap(a,b);
        swap(c,d);
    }
    long long tx[2]={inf,inf},ty[2]={inf,inf},nx[2],ny[2];
    tx[c]=f[a][c],ty[d]=f[b][d];
    for(int i=17;i>=0;i--)
        if(deep[jump[a][i]]>=deep[b])
        {
            nx[0]=nx[1]=inf;
            for(int j=0;j<=1;j++)
                for(int k=0;k<=1;k++)
                    nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
            tx[0]=nx[0],tx[1]=nx[1];
            a=jump[a][i];
        }
    if(a==b)
        return tx[d]+g[b][d];
    for(int i=17;i>=0;i--)
        if(jump[a][i]!=jump[b][i])
        {
            nx[0]=nx[1]=inf;
            ny[0]=ny[1]=inf;
            for(int j=0;j<=1;j++)
                for(int k=0;k<=1;k++)
                {
                    nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
                    ny[j]=min(ny[j],ty[k]+dp[b][i][k][j]);
                }
            tx[0]=nx[0],tx[1]=nx[1];
            ty[0]=ny[0],ty[1]=ny[1];
            a=jump[a][i];
            b=jump[b][i];
        }
    int lca=fa[a];
    return min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0],f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1])+g[lca][1]);
}
int main()
{
    cin>>n>>m>>type;
    int u,v;
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v,i);
        add(v,u,i+n);
    }
    deep[1]=1;
    build(1);
    DP(1);
    D_P(1);
    for(int i=1;i<=n;i++)
    {
        jump[i][0]=fa[i];
        dp[i][0][0][0]=2e15;
        dp[i][0][0][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
        dp[i][0][1][0]=f[fa[i]][0]-f[i][1];
        dp[i][0][1][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
    }
    for(int j=1;j<=17;j++)
        for(int i=1;i<=n;i++)
        {
            jump[i][j]=jump[jump[i][j-1]][j-1];
            for(int k=0;k<=1;k++)
                for(int l=0;l<=1;l++)
                {
                    dp[i][j][k][l]=2e15;
                    for(int q=0;q<=1;q++)
                        dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][q]+dp[jump[i][j-1]][j-1][q][l]);
                }
        }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&a,&c,&b,&d);
        if((fa[a]==b&&c==0&&d==0)||(fa[b]==a&&c==0&&d==0))
        {
            cout<<"-1\n";
            continue;
        }
        cout<<work()<<endl;
    }
}