首先不考虑强制要求的话是一个经典问题,令 \(f_{i, 0 / 1}\) 为 \(i\) 选或不选时以 \(i\) 为根的子树的最优答案。那么就有转移 \(f_{u, 0} = \sum f_{v, 1}, f_{u, 1} = \sum \min(f_{v, 0}, f_{v, 1})\).每次查询重新暴力 \(dp\) 一遍整棵树就可以获得 \(44pts\).

先考虑一个部分分,\(B1\) 的情况,可以发现我们每次强制一个点选或不选能影响到的是其到根一条路径上的 \(dp\) 值,那么如果我们每次询问就只需要修改一个点到根路径上的 \(dp\) 值即可,但是如果暴力修改复杂度还是不正确的,如果是菊花图就没了,但同时我们需要知道这样一个事情,通过上面的转移方程是可以发现这个 \(dp\) 值是满足可加性的,换句话说如果我们去除掉某个子树对答案的影响,那么其 \(dp\) 求出来的将会是剩余部分的最优解,也就是说我们的 \(dp\) 是可以分开考虑然后将 \(dp\) 值简单相加。于是我们可以记录 \(g_{i, 0 / 1}\) 表示 \(i\) 选或不选时去除掉 \(i\) 所在子树其父亲的最优解,那么这样我们每次往上重新 \(dp\) 的时候复杂度就是 \(O(dep)\) 了。

可以发现这个过程是可以使用倍增优化的,每次我们往父亲跳的过程可以直接倍增地跳,那么这样就可以快速重新 \(dp\) 出答案了。具体的我们可以令 \(dp_{i, j, 0 / 1}\) 表示在以 \(i\) 的 \(2 ^ j\) 祖先为根的子树中去除掉以 \(i\) 为根的子树部分 \(i\) 选或不选的答案,但是这样我们可以发现一个问题,每次往上跳之后我们并不知道当前跳到节点的状态,因此我们需要再添加一维状态令 \(dp_{i, j, 0 / 1, 0 / 1}\) 表示以 \(i\) 的 \(2 ^ j\) 祖先为根的子树中去除掉以 \(i\) 为根的子树部分 \(i\) 选或不选,\(i\) 的 \(2 ^ j\) 祖先选或不选的答案,那么这样我们就可以一路 \(dp\) 上去了,实现代码的时候最后两个点的 \(lca\) 往根节点跳的过程可以提前预处理出来这样就可以省掉大量的代码(令 \(g_{i, 0 / 1}\) 表示整棵树去除掉以 \(i\) 为根的子树 \(i\) 选或不选的答案),细节比较多,一定要想清楚细节再开始写代码。

#include<bits/stdc++.h>
using namespace std;
#define N 100000 + 5
#define M 20
#define inf 10000000000000000
#define rep(i, l, r) for(register int i = l; i <= r; ++i)
#define dep(i, l, r) for(register int i = r; i >= l; --i)
#define Next(i, u) for(register int i = h[u]; i; i = e[i].next)
typedef long long ll;
struct edge{
int v, next;
}e[N << 1];
char type[M];
ll f[N][2], g[N][2], dp[N][M][2][2];
int n, m, u, v, a, x, b, y, tot, h[N], p[N], dep[N], fa[N][M];
inline int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
inline void add(int u, int v){
e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
}
inline void dfs1(int u, int Fa){
fa[u][0] = Fa, f[u][1] += p[u], dep[u] = dep[Fa] + 1;
Next(i, u){
int v = e[i].v; if(v == Fa) continue;
dfs1(v, u);
f[u][0] += f[v][1], f[u][1] += min(f[v][0], f[v][1]);
}
}
inline void dfs2(int u, int fa){
if(u != 1){
g[u][0] = g[fa][1] + f[fa][1] - min(f[u][0], f[u][1]);
g[u][1] = min(g[fa][1] + f[fa][1] - min(f[u][0], f[u][1]), g[fa][0] + f[fa][0] - f[u][1]);
dp[u][0][0][0] = inf, dp[u][0][0][1] = f[fa][1] - min(f[u][0], f[u][1]);
dp[u][0][1][0] = f[fa][0] - f[u][1], dp[u][0][1][1] = f[fa][1] - min(f[u][0], f[u][1]);
}
Next(i, u) if(e[i].v != fa) dfs2(e[i].v, u);
}
inline ll solve(int x, int a, int y, int b){
if(dep[x] < dep[y]) swap(x, y), swap(a, b);
Next(i, y) if(e[i].v == x && !a && !b) return -1;
int fx = x, fy = y;
ll l[2] = {0, 0}, r[2] = {0, 0}, ans = 0, l0, l1, r0, r1;
l[!a] = inf, r[!b] = inf;
dep(i, 0, 17) if(dep[fa[x][i]] >= dep[y]){
l0 = l[0], l1 = l[1];
l[0] = min(l0 + dp[x][i][0][0], l1 + dp[x][i][1][0]);
l[1] = min(l0 + dp[x][i][0][1], l1 + dp[x][i][1][1]);
x = fa[x][i];
}
if(x != y){
dep(i, 0, 17) if(fa[x][i] != fa[y][i]){
l0 = l[0], l1 = l[1], r0 = r[0], r1 = r[1];
l[0] = min(l0 + dp[x][i][0][0], l1 + dp[x][i][1][0]);
l[1] = min(l0 + dp[x][i][0][1], l1 + dp[x][i][1][1]);
r[0] = min(r0 + dp[y][i][0][0], r1 + dp[y][i][1][0]);
r[1] = min(r0 + dp[y][i][0][1], r1 + dp[y][i][1][1]);
x = fa[x][i], y = fa[y][i];
}
l0 = l[0], l1 = l[1], r0 = r[0], r1 = r[1];
ll l0r0, l0r1, l1r0, l1r1, lca = fa[x][0];
l0r0 = (dp[x][0][0][0] + dp[y][0][0][0] - f[y][1] - f[x][1]) / 2 + g[lca][0];
l0r0 = min(l0r0, (dp[x][0][0][1] + dp[y][0][0][1] - min(f[x][0], f[x][1]) - min(f[y][0], f[y][1])) / 2 + g[lca][1]);
l0r0 += l0 + r0;
l0r1 = (dp[x][0][0][0] + dp[y][0][1][0] - f[y][1] - f[x][1]) / 2 + g[lca][0];
l0r1 = min(l0r1, (dp[x][0][0][1] + dp[y][0][1][1] - min(f[x][0], f[x][1]) - min(f[y][0], f[y][1])) / 2 + g[lca][1]);
l0r1 += l0 + r1;
l1r0 = (dp[x][0][1][0] + dp[y][0][0][0] - f[y][1] - f[x][1]) / 2 + g[lca][0];
l1r0 = min(l1r0, (dp[x][0][1][1] + dp[y][0][0][1] - min(f[x][0], f[x][1]) - min(f[y][0], f[y][1])) / 2 + g[lca][1]);
l1r0 += l1 + r0;
l1r1 = (dp[x][0][1][0] + dp[y][0][1][0] - f[y][1] - f[x][1]) / 2 + g[lca][0];
l1r1 = min(l1r1, (dp[x][0][1][1] + dp[y][0][1][1] - min(f[x][0], f[x][1]) - min(f[y][0], f[y][1])) / 2 + g[lca][1]);
l1r1 += l1 + r1;
x = fa[x][0], y = fa[y][0];
ans += min(min(min(l0r0, l0r1), l1r0), l1r1) + f[fx][a] + f[fy][b];
}
else{
ans += f[fx][a];
l[!b] = inf;
ans += min(l[0] + g[y][0], l[1] + g[y][1]);
}
return ans;
}
signed main(){
n = read(), m = read(), scanf("%s", type + 1);
rep(i, 1, n) p[i] = read();
rep(i, 1, n - 1) u = read(), v = read(), add(u, v), add(v, u);
dfs1(1, 0), dfs2(1, 0);
rep(j, 1, 17) rep(i, 1, n){
int Fa = fa[i][j - 1];
fa[i][j] = fa[Fa][j - 1];
dp[i][j][0][0] = min(dp[i][j - 1][0][0] + dp[Fa][j - 1][0][0], dp[i][j - 1][0][1] + dp[Fa][j - 1][1][0]);
dp[i][j][0][1] = min(dp[i][j - 1][0][0] + dp[Fa][j - 1][0][1], dp[i][j - 1][0][1] + dp[Fa][j - 1][1][1]);
dp[i][j][1][0] = min(dp[i][j - 1][1][0] + dp[Fa][j - 1][0][0], dp[i][j - 1][1][1] + dp[Fa][j - 1][1][0]);
dp[i][j][1][1] = min(dp[i][j - 1][1][0] + dp[Fa][j - 1][0][1], dp[i][j - 1][1][1] + dp[Fa][j - 1][1][1]);
}
while(m--) a = read(), x = read(), b = read(), y = read(), printf("%lld\n", solve(a, x, b, y));
return 0;
}

NOIP2018 Day2T3 保卫王国的更多相关文章

  1. 「NOIP2018」保卫王国

    「NOIP2018保卫王国」 题目描述 有一棵 \(n\) 个点, 点有点权 \(a_i\),\(m\) 组询问, 每次求钦点两个节点必须选或者必须不选后的树上最小点覆盖. \(1 \leq n, m ...

  2. noip2018 d2t3 保卫王国 解题报告

    保卫王国 电脑卡懒得把题面挪过来了. 朴素 \[ dp_{i,0}=\sum dp_{s,1}\\ dp_{i,1}=\sum \min(dp_{s,0},dp_{s,1})+p_i \] 然后直接动 ...

  3. 【NOIP2018】保卫王国 动态dp

    此题场上打了一个正确的$44pts$,接着看错题疯狂$rush$“正确”的$44pts$,后来没$rush$完没将之前的代码$copy$回去,直接变零分了..... 这一题我们显然有一种$O(nm)$ ...

  4. loj 2955 「NOIP2018」保卫王国 - 树链剖分 - 动态规划

    题目传送门 传送门 想抄一个短一点ddp板子.然后照着Jode抄,莫名其妙多了90行和1.3k. Code /** * loj * Problem#2955 * Accepted * Time: 26 ...

  5. @NOIP2018 - D2T3@ 保卫王国

    目录 @题目描述@ @题解@ @代码@ @题目描述@ Z 国有n座城市,n−1 条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达. Z 国的国防部长小 Z 要在城市中驻 ...

  6. 竞赛题解 - NOIP2018 保卫王国

    \(\mathcal{NOIP2018}\) 保卫王国 - 竞赛题解 按某一个炒鸡dalao名曰 taotao 的话说: \(\ \ \ \ \ \ \ \ \ "一道sb倍增题" ...

  7. LG5024 保卫王国

    题意 题目描述 Z 国有\(n\)座城市,\(n - 1\)条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达. Z 国的国防部长小 Z 要在城市中驻扎军队.驻扎军队需要 ...

  8. Uoj 441 保卫王国

    Uoj 441 保卫王国 动态 \(dp\) .今天才来写这个题. 设 \(f[u][0/1]\) 表示子树 \(u\) 中不选/选 \(u\) 时的最小权值和,显然有:\(f[u][0]=\sum ...

  9. [NOIP2018TG]保卫王国

    [NOIP2018TG]保卫王国 BZOJ luogu 当动态dp模板题写的,(全集-最大点权独立集)不能放军队的+inf,必须放军队-inf即可 注意矩阵乘法的顺序问题 #define ll lon ...

随机推荐

  1. Linux-saltstack-2 saltstack的基本使用

    @ 目录 一.salt命令的基本使用 1.基本语法 例子: 2.salt的常用参数 (1)-S(大写):通过IP或者是网段匹配被管理主机 (2)-E:通过正则匹配主机 (3)-L: 匹配多个主机 (4 ...

  2. oracle 之 cursor:创建存储过程批量执行DDL语句

    说明:使用此过程可任意执行批量DDL语句,调用DDL查询语句时,注意转义字符,使用 ' 转义! 需求:批量删除以CUR_TEST开头的表,且有日志记录. 环境准备:建几张以CUR_TEST开头测试表. ...

  3. nginx中请求大小的限制的设置

    Nginx对客户端请求缓冲区大小有个默认限制,如果超过了该值(比如在上传大文件时),会报500错误. 只需要设置三个值,就可以解决该问题: 1. client_body_buffer_size: 指定 ...

  4. git报错 error: cannot stat 'file': Permission denied

    切换分支(git checkout xxx)时报错: error: cannot stat 'file': Permission denied 解决方法:退出编辑器.浏览器.资源管理器等,然后再切换就 ...

  5. 安装KVM

    在VMWare安装CentOS7 选择图形界面和开发工具 设置网络 cd /etc/sysconfig/network-scripts/ vi ifcfg-ens33 BOOTPROTO=static ...

  6. java 封装 总结

    1.前言 老是被问什么是java 封装...很基础的一个问题 ,其实我们一直在写的东西但不知道怎么称呼. 比如 在entity实体类 里面老用到的 getter 和 setter 方法其实就是封装的方 ...

  7. spring boot 启动读取的配置文件优先级

    1.优先级从高到低 1.  file:/config/ 2. file:/ 3. classpath:/config/ 4. classpath:/ 所有位置的application.properti ...

  8. vue中动画的使用

    不要在router-view的外层使用动画!不要在router-view的外层使用动画!不要在router-view的外层使用动画! 重要的事情说三遍,在app.vue中自以为奇思妙想(实际是脑残)在 ...

  9. 封装OCX

    封装OCX的办法有2种: 1. 使用C++的MFC activex项目生成OCX 2. 使用C#的用户控件生成OCX(.net core好像不支持) 注意:以管理员身份运行Visual Studio ...

  10. Termux劣质的入门指南

    直入主题: 1.1 下载安装 Google下载(有条件的用!) F-droid下载(建议使用!) 也可以去酷安下载! 1.2 配置 apt update && apt upgrade ...