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. Subversion客户端接受服务器证书出现“The certificate hostname does not match”的问题

    当使用https://形式的URL连接Apache时,Subversion客户端将会受到两个类型的响应: 1.一个服务器证书 2.一个针对客户端证书的请求 在本人的应用场景主要涉及SVN仓库迁移的操作 ...

  2. Maven依赖中的scope详解,在eclipse里面用maven install可以编程成功,到服务器上用命令执行报VM crash错误

    Maven依赖中的scope详解 项目中用了<scope>test</scope>在eclipse里面用maven install可以编译成功,到服务器上用命令执行报VM cr ...

  3. Codeforce 287A - IQ Test (模拟)

    In the city of Ultima Thule job applicants are often offered an IQ test. The test is as follows: the ...

  4. How to use CAR FANS C800 Diagnostic Scan Tool to do diagnosis operation

    How to use Heavy Duty Diagnostic CAR FANS C800 Diagnostic Scan Tool to do diagnosis operation Here i ...

  5. Python框架----cookie和session

    一.cookie和session的介绍 cookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生. cookie ...

  6. SQL Server中调用WebService

    首先要启用Ole Automation Procedures,使用sp_configure 配置时如果报错"不支持对系统目录进行即席更新",可以加上WITH OVERRIDE选项. ...

  7. sql server 触发器的简单用法

    触发器  -- 一下写的都是我对触发器的理解 当在执行insert . delete . 等操作的时候 随便要做一些额外的操作, 比如在添加的时候就会将新添加的数据存到inserted表中 写个实例 ...

  8. Tomcat启动报错:[Failed to start component]的解决方案

    在MyEclipse中启动Tomcat,该Tomcat仅部署了一个报错项目,启动Tomcat Server的全部信息如下: usage: java org.apache.catalina.startu ...

  9. kivy中文

    from kivy.config import Config Config.set('kivy', 'default_font', [ 'msgothic', 'DroidSansFallback.t ...

  10. django实现类似触发器的效果

    https://blog.csdn.net/pushiqiang/article/details/50652080?utm_source=blogxgwz1 https://blog.csdn.net ...