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;
}
}

【NOIP 2018】Day2 T3 保卫王国的更多相关文章

  1. 【NOIP 2013 DAY2 T3】 华容道(spfa)

    题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间. 小 ...

  2. 【NOIP 2015 DAY2 T3】 运输计划 (树链剖分-LCA)

    题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家 ...

  3. noip 2018 Day2 T1 旅行

    暴力删边,暴力枚举 #include <bits/stdc++.h> using namespace std; #define MAXM 5010 inline int read() { ...

  4. noip 2018 day1 T3 赛道修建 贪心_树上问题_multiset

    Code: // luogu-judger-enable-o2 #include<bits/stdc++.h> using namespace std; #define maxn 5000 ...

  5. noip 2018 day2 T1 旅行 基环树 tarjan

    Code: #include<cstdio> #include<cstring> #include<string> #include<stack> #i ...

  6. 二分答案 + multiset || NOIP 2018 D1 T3 || Luogu P5021 赛道修建

    题面:P5021 赛道修建 题解:二分答案,用Dfs进行判断,multiset维护. Dfs(x,fa,Lim)用来计算以x为根的子树中有多少符合条件的路径,并返回剩余未使用的最长路径长. 贪心思想很 ...

  7. [NOIp 2018]all

    Description 题库链接: Day1 T1 铺设道路 Day1 T2 货币系统 Day1 T3 赛道修建 Day2 T1 旅行 Day2 T2 填数游戏 Day2 T3 保卫王国 Soluti ...

  8. 【NOIP 2017】Day2 T3 列队

    Problem Description \(Sylvia\) 是一个热爱学习的女孩子. 前段时间,\(Sylvia\) 参加了学校的军训.众所周知,军训的时候需要站方阵. \(Sylvia\) 所在的 ...

  9. NOIP 2018 总结

    NOIP 2018 总结 提高组: 应得分 \(100 + 100 + 40 + 100 + 50 + 44 = 434\). 考后期望得分 \(100 + 100 + 20 + 100 + 50 + ...

随机推荐

  1. RPC框架小结

    为什么说要搞定微服务架构,先搞定RPC框架? 1. 为什么说要搞定微服务架构,先搞定RPC框架? 如果没有统一的服务框架,RPC框架,各个团队的服务提供方就需要各自实现一套序列化.反序列化.网络框架. ...

  2. 1113: No mapping for the Unicode character exists in the target multi-byte code page

    windows版本nginx启动 报错. 启动方式:到nginx所在目录执行:nginx.exe -c conf\nginx.conf 原因:所在路径中含有中文字符. 解决:换个没有中文的路径.

  3. FAQ About WOYO PDR007 Dent Removal Heat Induction System

    WOYO PDR 007 is a dent repair tool for auto maintence. WOYO PDR007 Auto Body Paintless Dent Repair K ...

  4. The Little Prince-12/12

    The Little Prince-12/12 双十二,大家有没有买买买呢?宝宝双十一之后就吃土了,到现在,叶子都长出来了!!! 当你真的喜欢一个人的时候 就会想很多 会很容易办蠢事 说傻话 小王子要 ...

  5. springboot打包部署到tomcat

    一. springboot打成war包: 1. 首先查看是否为war 2. File----->ProjectStruture,选择Artifacts,中部点击“+”号 3. 按图中标记进行选择 ...

  6. Python进阶【第二篇】编写Python代码

    一.第一句Python代码——Hello Word 在 /home/dev/ 目录下创建 hello.py 文件,内容如下: print "hello,world" 执行 hell ...

  7. Spring Boot(十六):使用Jenkins部署Spring Boot

    Spring Boot(十六):使用Jenkins部署Spring Boot jenkins是devops神器,介绍如何安装和使用jenkins部署Spring Boot项目 jenkins搭建 部署 ...

  8. Spring Boot(九):定时任务

    Spring Boot(九):定时任务 一.pom包配置 pom包里面只需要引入springboot starter包即可 <dependencies> <dependency> ...

  9. shell脚本一键安装nginx

    依赖包安装包放在一起, 直接执行这个脚本就行. #!/bin/bash #--------------------------------------------------------------- ...

  10. mvc 前端校验

    首先解决 Ajax.BeginFor异步提交表单,给表单添加样式的问题.不能直接用class属性,网上找了很多都是用ClassName,经过测试不管用,看源代码发现生成的是ClassName而非cla ...