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. c# ref和out参数

    向方法传递参的时候,对应的参数通常会用实参的拷贝来初始化.就是说随便在方法内部进行怎样的修改,都不会影响作为参数传递的变量的原始值. 通过上面的例子我们可以看出来,如果一个方法的参数是引用类型,那么使 ...

  2. EasyUi通过OCUpload上传及POI上传 实现导入xls表格功能

    Easyui上传文件案例 第一步:要想使用OCUpload首先前端需要导入js包         <script type="text/javascript" src=&qu ...

  3. android搜索框列表布局,流程及主要步骤思维导图

    android搜索框列表布局,流程及主要步骤思维导图 android搜索框列表布局,流程及主要步骤思维导图 activity_coin_search.xml----------<com.scwa ...

  4. python colorama模块

    colorama是一个python专门用来在控制台.命令行输出彩色文字的模块,可以跨平台使用. 1. 安装colorama模块 pip install colorama 可用格式常数: Fore: B ...

  5. Installing Android Studio

    To set up Android Studio on Windows: Launch the .exe file you just downloaded. Follow the setup wiza ...

  6. 一款用于对 WiFi 接入点安全进行渗透测试的工具

    越来越多的设备通过无线传输的方式连接到互联网,以及,大范围可用的 WiFi 接入点为攻击者攻击用户提供了很多机会.通过欺骗用户连接到虚假的 WiFi 接入点,攻击者可以完全控制用户的网络连接,这将使得 ...

  7. 2019/3/19 wen 运算符

  8. be动词

    编辑 讨论 be动词,意思和用法很多,一般的意思是:是,此种用法,有多种变化形式,is,am,are,was,were,being,been,to be.另外,be动词还有成为的意思.根据句子中不同的 ...

  9. RHEL6/7 x86_64下cachefilesd占用cpu达到100%

    昨天,有个测试环境cachedfilesd CPU 100%,一直在跑了挺久,经查 1. CacheFiles介绍NFS是一种经常使用到的网络共享文件系统,在分布式环境下,多台服务器的文件共享是一个问 ...

  10. Nginx 容器教程

    春节前,我看到 Nginx 加入了 HTTP/2 的 server push 功能,就很想试一下. 正好这些天,我在学习 Docker,就想到可以用 Nginx 容器.万一哪里改乱了,直接删掉,再重启 ...