【NOIP 2018】Day2 T3 保卫王国

Problem

Description

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

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

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

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

Input Format

第 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\) 必须驻扎军队。

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

Output Format

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

Sample

Input

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

Output

12 
7 
-1

Explanation

Explanation for Input

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

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

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

Range

对于全部数据,\(n=m\le 10^5,1\le p_i\le 10^5\)

测试点编号 $\text{type}$ $n=$
$1,2$ A3 $10$
$3,4$ C3
$5,6$ A3 $100$
$7$ C3
$8,9$ A3 $2\times 10^3$
$10,11$ C3
$12,13$ A1 $10^5$
$14\sim 16$ A2
$17$ A3
$18,19$ B1
$20,21$ C1
$22$ C2
$23\sim 25$ C3

数据类型的含义:

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

Algorithm

倍增,树形DP

Mentality

首先看到数据范围为 \(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\) 呗......

Code

这个地方我分开写伐。

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 <cstdio>
#include <cstring>
#include <iostream>
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;
  }
}

posted @ 2019-01-30 08:12  洛水·锦依卫  阅读(405)  评论(0编辑  收藏  举报