学习自: @秦淮岸
Force
如果没有限制条件,那么这道题就跟战略游戏 \(or\) 没有上司的舞会是一样的,只需\(dp\)一次就够了,所以很容易想到一个 \(44pts\) 的暴力:对于每次询问,都跑一遍 \(dp\),其中让 \(a, b\) 两个点强制转移(放/放)即可。
Thoughts
无解情况
显然,一般来说都是有解的,除非:
- 命令冲突,即\(a = b\) ,且 \(x \not= y\)
- \((a, b)\) 有一条边,即存在父子关系,而两个都不能放,即 \(x = 0 \& y = 0\)
分部分
发现若用暴力,很多地方的 \(dp\) 显然都是相同的。
这道题核心是:相邻两个点上必须有一个点有守卫
那么我们考虑把每次询问,将整张图分成若干个好预处理的部分,每个部分分开处理,让每个部分内部符合条件,让每个部分交界处满足条件。因为每个部分互不干扰,所以可以相对取 \(min\) 最优解。
考虑简单的情况, \((a, b)\) 在一条链上:
PS:\((a, b)\) 是对称的,如果\(a\)在底下咱们\(swap\)一下就行了
那么如果\((a, b)\) 不在一条链上,我们可以把它看做两条链:
- \(a\) 到 \(lca\)
- \(lca\) 到 \(b\)
具体情况如下图
所以,一切 \((a, b)\) 询问我们都可以归为以上两种情况,我们只需要用预处理大法求出来每一个部分的最小花费,求和就行了!
注意,为了不让交点重复计算,我们可以让橙色部分不算\(a, b\)的花费,紫色部分不算\(lca\)的花费
蓝色部分
\(f[u][0 / 1]\) 表示以 \(u\) 为子树, \(u\) 不选 / 选 下面的最大价值
转移 :
\(f[u][1] = p[u]\)
\(f[u][0] += f[v\)][1]
\(f[u][1] += min(f[v][0], f[v][1])\)
这跟暴力、没有上司的舞会 / 战略游戏就是一样的嘛。
紫色部分
\(g[u][0 / 1]\) 表示除了 \(u\) 的子树,剩下的 不选 / 选 下面的最大价值
转移:
\(g[v][1] = p[v]\)
\(g[v][0] = g[u][1] + f[u][1] - min(f[v][0], f[v][1]);\)
$g[v][1] = min(g[v][0], g[u][0] + f[u][0] - f[v][1]); $
这个思想类似于 1073. 树的中心,从父亲向儿子 \(dp\)
橙色部分
因为不论怎样我们都要搞 \(lca\),所以我们不妨边倍增跳,边求解。顺便维护倍增最小花费。
$w[u][0 / 1][i][0 / 1] $
设 \(z = u\) 往上跳 \(2 ^ i\) 步的点
\(u\) 不选/选;\(z\)不选 / 选。 以 \(z\) 的子树中,不包含 \(u\) 子树的最小花费。
初始化,这里的转移由蓝色部分逆推回去,既然 \(f[u][1 / 0]\)代表以 \(u\)为子树的最小花费,我再减掉相应 \(v\) 的贡献不就行了吗?
\(w[v][0][0][0] = -inf\)
\(w[v][0][0][1] = f[u][1] - min(f[v][0], f[v][1])\)
\(w[v][1][0][0] = f[u][0] - f[v\)][1]
\(w[v][1][0][1] = min(w[v][0][0][1], w[v][1][0][0]);\)
倍增,枚举中间点选不选:
\(w[u][x][i][y] = min(w[u][x][i][y], w[u][x][i - 1][z] + w[fa[u][i - 1]][z][i - 1][y]); (0 <= x, y, z <= 1)\)
最后\(dp\)求解,与倍增数组 \(w\) 的处理方式一样,倍增跳即可,注意,\(LCA\)分选 \(or\) 不选两种情况取 \(min\)。
时间复杂度
预处理 \(O(Nlog_2N)\),每次询问 \(O(Mlog_2N)\)
故总复杂度 \(O((N + M)log_2N)\)
#include <cstdio> #include <iostream> #include <cstring> using namespace std; typedef long long LL; const int N = 100005, L = 18; int n, m, p[N]; char type[10]; int head[N], numE = 0; LL f[N][2], g[N][2], w[N][2][L][2]; int fa[N][L], dep[N]; struct E{ int next, v; }e[N << 1]; void add(int u, int v) { e[++numE] = (E) { head[u], v }; head[u] = numE; } void dfs0(int u) { f[u][1] = p[u]; for (int i = 1; i < L && fa[u][i - 1]; i++){ fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for (int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(v == fa[u][0]) continue; dep[v] = dep[u] + 1; fa[v][0] = u; dfs0(v); f[u][0] += f[v][1]; f[u][1] += min(f[v][0], f[v][1]); } } void dfs2(int u) { for (int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(v == fa[u][0]) continue; w[v][0][0][1] = f[u][1] - min(f[v][0], f[v][1]); w[v][1][0][0] = f[u][0] - f[v][1]; w[v][1][0][1] = w[v][0][0][1]; dfs2(v); } } void dfs1(int u) { for (int i = 1; i < L && fa[fa[u][i - 1]][i - 1]; i++) { for (int x = 0; x <= 1; x++) { for (int y = 0; y <= 1; y++) { for (int z = 0; z <= 1; z++) { w[u][x][i][y] = min(w[u][x][i][y], w[u][x][i - 1][z] + w[fa[u][i - 1]][z][i - 1][y]); } } } } for (int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(v == fa[u][0]) continue; g[v][0] = g[u][1] + f[u][1] - min(f[v][0], f[v][1]); g[v][1] = min(g[v][0], g[u][0] + f[u][0] - f[v][1]); dfs1(v); } } LL inline dp(int a, int x, int b, int y) { LL nx[2], ny[2], tx[2], ty[2]; // nx, ny 表示从开始跳到当前的 a, b 点,这个点放不放军队的最小花费 if(dep[a] < dep[b]) { swap(x, y); swap(a, b); } // 先跳到同一深度 memset(tx, 0x3f, sizeof tx); memset(ty, 0x3f, sizeof ty); tx[x] = f[a][x], ty[y] = f[b][y]; for (int i = L - 1; ~i; i--) { if(dep[a] - (1 << i) < dep[b]) continue; memset(nx, 0x3f, sizeof nx); for (int u = 0; u <= 1; u++) { // u:上一个选不选;v 这一个选不选 for (int v = 0; v <= 1; v++) { nx[v] = min(nx[v], tx[u] + w[a][u][i][v]); } } memcpy(tx, nx, sizeof nx); a = fa[a][i]; } // 一条链的情况直接返回 if(a == b) return tx[y] + g[b][y]; // 一起挖往上跳 for (int i = L - 1; ~i; i--) { if(fa[a][i] == fa[b][i]) continue; memset(nx, 0x3f, sizeof nx); memset(ny, 0x3f, sizeof ny); for (int u = 0; u <= 1; u++) { for (int v = 0; v <= 1; v++) { // u:上一个选不选;v 这一个选不选 nx[v] = min(nx[v], tx[u] + w[a][u][i][v]); ny[v] = min(ny[v], ty[u] + w[b][u][i][v]); } } memcpy(tx, nx, sizeof nx); memcpy(ty, ny, sizeof ny); a = fa[a][i]; b = fa[b][i]; } int lca = fa[a][0]; // res0:不选 lca; res1:选lca LL res0 = g[lca][0] + f[lca][0] - f[a][1] - f[b][1] + tx[1] + ty[1]; LL res1 = g[lca][1] + f[lca][1] - min(f[a][1], f[a][0]) - min(f[b][0], f[b][1]) + min(tx[1], tx[0]) + min(ty[1], ty[0]); return min(res0, res1); } int main(){ // 读入 memset(w, 0x3f, sizeof w); scanf("%d%d%s", &n, &m, type); for (int i = 1; i <= n; i++) scanf("%d", p + i); for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); add(u, v); add(v, u); } dep[1] = 1; // 处理 dep, fa, f 数组 dfs0(1); // 初始化 w 数组 dfs2(1); // 倍增 w 数组 + 处理 g 数组 dfs1(1); // 询问 for (int i = 1, a, x, b, y; i <= m; i++) { scanf("%d%d%d%d", &a, &x, &b, &y); // 无解判断:仅当 x -> y 存在一条边且两个都不让放,就GG了 if(!x && !y && (fa[a][0] == b || fa[b][0] == a)) { puts("-1"); } else { printf("%lld\n", dp(a, x, b, y)); } } return 0; }