【NOIP 2018】保卫王国(动态dp / 倍增)
这个$dark$题,嗯,不想说了。
法一:动态$dp$
虽然早有听闻动态$dp$,但到最近才学,如果你了解动态$dp$,那就能很轻松做出这道题了。故利用这题在这里科普一下动态$dp$的具体内容。
我们先不考虑点上的强制选不选的限制,这是一个最小权边覆盖问题,大家肯定都会这道题的$O(nm)$的做法,这是一个很经典的树形$dp$。具体来讲就是一下两个转移:
$$f_{x, 0} = \sum_{v} f_{v, 1} \qquad f_{x, 1} = a_{x} + \sum_{v} min(f_{v, 0} , f_{v, 1})$$
其中$f_{x, 0/1}$表示$x$这个点选/不选时$x$这个子树下的最少花费,$v$是$x$的亲儿子。
问题在树上,我们通常考虑树链剖分,并用$s(x)$表示$x$的重儿子。同时我们引出有关$x$新函数$g$如下:
$$g_{x, 0} = \sum_{v, v \neq s(x)} f_{v, 1} \qquad g_{x, 1} = a_{x} + \sum_{v, v \neq s(x)} min(f_{v, 0}, f_{v, 1})$$
于是有关$f$的转移可以改写成:
$$f_{x, 0} = f_{s(x), 1} + g_{x, 0} \qquad f_{x, 1} = min(f_{s(x), 0}, f_{s(x), 1}) + g_{x, 1}$$
这么做的目的在于把重儿子单独分离开来,这样在$g$中是不包含重儿子的信息的。我们过一会就能看到它的用处。
上述改写后的是一个有加法和取$min$的一个转移,我们把矩阵乘法中的乘法变成加法,把加法变成取$min$,那我们可以用一个线性变换来描述它,我们称它为$x$上的矩阵:
$$\begin{bmatrix}\infty & g_{x,0} \\g_{x,1} & g_{x, 1} \end{bmatrix}\begin{pmatrix} f_{s_{x},0} \\f_{s_x,1}\end{pmatrix}=\begin{pmatrix}f_{x,0} \\f_{x,1}\end{pmatrix}$$
特别的,我们有单位矩阵: $\begin{bmatrix}0 & \infty \\\infty & 0 \end{bmatrix}$。
这么做的好处在于原本一个自下而上的$dp$,可以被转变为矩阵乘法,一个点$x$的$f$可以由$x$点到它所在的重链的链尾上所有矩阵的乘积表示。我们可以用线段树维护链上矩阵的乘积,就能快速算得我们想要的$dp$值。
我们考虑如果要修改某一个点$x$的点权,我们如何维护矩阵的变化。首先我们都知道只有$x$的祖先的$dp$值可能会变化,并且如果$x$所在的儿子是某个祖先$y$的重儿子,那$g_y$就不会变化。由于我们的矩阵中只有关于$g$的信息,故$y$的矩阵也不会变化。所以事实上会发生变化的矩阵只有祖先链上的$O(logn)$条轻边的父亲的矩阵。我们可以自下而上每次暴力跳到那几条轻边,先在线段树上查得轻边儿子的$f$,然后把它父亲的$g$更新,修改矩阵。那么我们就能$O(log^2n)$维护点权修改了。注意这里我们每次会重新算链头的$f$值,所以任意时刻链头的$f$值都是对的,而非链头的点的$f$值是不一定准确的。
这就是动态$dp$的大致内容,我们可以整理一下思路。首先我们把$dp$的过程用线性变换替代,于是用矩阵的乘积表示某点的$dp$值。对于每次修改,我们暴力跳轻边来更新矩阵。
现在我们已经知道如何在支持修改点权的情况下,动态维护一棵子树下的最小权边覆盖问题。回过头来看这道题就显得非常容易了,题中的限制条件就可以通过把点权设成$-inf/inf$来实现。
这里我把矩阵乘法手动展开了,大概能快$400ms$左右。
#include <cstdio>
#include <algorithm> using namespace std; typedef long long LL; const int N = ;
const LL INF = (LL)1e17;
const LL BINF = INF / ; int n, nq;
int fa[N], tp[N], so[N], si[N], df[N], dw[N], li[N];
LL val[N], g[N][], f[N][]; struct Mat {
LL v[][];
Mat(LL a = , LL b = ) {
v[][] = INF, v[][] = a;
v[][] = v[][] = b;
}
friend Mat operator * (Mat &a, Mat &b) {
static Mat c;
c.v[][] = min(a.v[][] + b.v[][], a.v[][] + b.v[][]);
c.v[][] = min(a.v[][] + b.v[][], a.v[][] + b.v[][]);
c.v[][] = min(a.v[][] + b.v[][], a.v[][] + b.v[][]);
c.v[][] = min(a.v[][] + b.v[][], a.v[][] + b.v[][]);
return c;
}
} I; int yu, la[N], to[N << ], pr[N << ];
inline void Ade(int a, int b) {
to[++yu] = b, pr[yu] = la[a], la[a] = yu;
} void Dfs0(int x, int fat) {
si[x] = , f[x][] = val[x];
for (int i = la[x]; i; i = pr[i]) {
if (to[i] == fat) continue;
fa[to[i]] = x;
Dfs0(to[i], x);
si[x] += si[to[i]];
if (si[to[i]] > si[so[x]]) so[x] = to[i];
f[x][] += f[to[i]][];
f[x][] += min(f[to[i]][], f[to[i]][]);
}
}
void Dfs1(int x, int gr) {
li[df[x] = ++*li] = x;
tp[x] = gr, dw[x] = x, g[x][] = val[x];
if (so[x]) Dfs1(so[x], gr), dw[x] = dw[so[x]];
for (int i = la[x]; i; i = pr[i])
if (to[i] != fa[x] && to[i] != so[x]) {
Dfs1(to[i], to[i]);
g[x][] += f[to[i]][];
g[x][] += min(f[to[i]][], f[to[i]][]);
}
} namespace SE {
int B;
Mat t[N << | ];
void Bu(int n) {
for (B = ; B < n + ; B <<= );
for (int i = ; i <= n; ++i)
t[B + i] = Mat(g[li[i]][], g[li[i]][]);
for (int i = B - ; i; --i) t[i] = t[i << ] * t[i << | ];
}
void Mo(int x) {
t[x + B] = Mat(g[li[x]][], g[li[x]][]);
for ((x += B) >>= ; x; x >>= ) t[x] = t[x << ] * t[x << | ];
}
Mat Qr(int l, int r) {
Mat r0 = I, r1 = I;
for (l += B - , r += B + ; l ^ r ^ ; l >>= , r >>= ) {
if (~l & ) r0 = r0 * t[l ^ ];
if (r & ) r1 = t[r ^ ] * r1;
}
return r0 * r1;
}
} void Modify(int x, LL _v) {
g[x][] += _v - val[x], val[x] = _v;
for (; x; x = fa[x]) {
SE::Mo(df[x]), x = tp[x];
Mat tf = SE::Qr(df[x], df[dw[x]]);
g[fa[x]][] -= f[x][];
g[fa[x]][] -= min(f[x][], f[x][]);
f[x][] = tf.v[][], f[x][] = tf.v[][];
g[fa[x]][] += f[x][];
g[fa[x]][] += min(f[x][], f[x][]);
}
} int main() {
I.v[][] = I.v[][] = INF;
I.v[][] = I.v[][] = ;
scanf("%d%d%*s", &n, &nq);
for (int i = ; i <= n; ++i)
scanf("%lld", &val[i]);
for (int i = , x, y; i < n; ++i) {
scanf("%d%d", &x, &y);
Ade(x, y), Ade(y, x);
}
Dfs0(, ), Dfs1(, );
SE::Bu(n); for (int a, b, x, y; nq--; ) {
scanf("%d%d%d%d", &x, &a, &y, &b);
LL lx = val[x], ly = val[y];
Modify(x, a? lx - BINF : BINF);
Modify(y, b? ly - BINF : BINF);
LL ans = min(f[][], f[][]);
ans += (a? BINF : ) + (b? BINF : );
printf("%lld\n", ans < BINF / ? ans : -);
Modify(x, lx);
Modify(y, ly);
}
return ;
}
法二:倍增$dp$
由于这道题并没有涉及点权修改,我们可以用倍增在实现$dp$的快速转移。设$f_{x, 0/1}$表示$x$点的子树下,$x$点选与不选时的最小花费,设$g_{x, 0/1}$表示除了$x$子树外的树的其他部分,在$x$点选与不选时的最小花费。显然这个可以$O(n)$树形$dp$出来。
在令$h_{x, i, 0/1, 0/1}$表示$x$的$2^i$级祖先$y$的子树下,并用$0/1$表示两者的状态时的最小花费,这个可以$O(nlogn)$求出来,和普通的倍增一样,合并时枚举几个点的状态即可。
在求每个询问的答案时,如果两个点$x,y$,其中$y$是$x$的祖先,那么可以直接倍增上去;否则$x,y$都倍增到$lca$的亲儿子上,最后再枚举状态求一下就好了。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; typedef long long LL; const int LG = ;
const int N = ;
const LL INF = (LL)1e17; int n, nq;
int val[N], dep[N], gr[LG][N];
LL f[][N], g[][N]; int yu, la[N], pr[N << ], to[N << ];
inline void Ade(int a, int b) {
to[++yu] = b, pr[yu] = la[a], la[a] = yu;
} struct Mat {
LL v[][];
Mat(LL x = INF, LL y = INF) {
v[][] = x, v[][] = y;
v[][] = v[][] = INF;
}
friend Mat Mul(Mat a, Mat b, int y) {
static Mat c; // a = x -> y, b = y -> z
for (int i = ; i < ; ++i)
for (int k = ; k < ; ++k)
c.v[i][k] = min(b.v[][k] - f[][y] + a.v[i][], b.v[][k] - f[][y] + a.v[i][]); // inf
return c;
}
} h[LG][N]; void Dfs0(int x, int fat) {
f[][x] = val[x];
for (int i = la[x]; i; i = pr[i]) {
int v = to[i];
if (v != fat) {
dep[v] = dep[x] + ;
Dfs0(v, x);
gr[][v] = x;
f[][x] += f[][v];
f[][x] += min(f[][v], f[][v]);
}
}
}
void Dfs1(int x, int fat) {
for (int i = la[x]; i; i = pr[i]) {
int v = to[i];
Mat &h0 = h[][v];
if (v != fat) {
LL t0 = g[][x] + f[][x] - f[][v];
LL t1 = g[][x] + f[][x] - min(f[][v], f[][v]);
g[][v] = t1;
g[][v] = min(t0, t1);
Dfs1(v, x);
h0.v[][] = INF;
h0.v[][] = f[][x];
h0.v[][] = f[][x] - min(f[][v], f[][v]) + f[][v];
h0.v[][] = f[][x] - min(f[][v], f[][v]) + f[][v];
}
}
} int main() {
scanf("%d%d%*s", &n, &nq);
for (int i = ; i <= n; ++i)
scanf("%d", &val[i]);
for (int i = , x, y; i < n; ++i) {
scanf("%d%d", &x, &y);
Ade(x, y), Ade(y, x);
}
dep[] = , Dfs0(, ), Dfs1(, );
for (int r = ; r < LG; ++r) {
for (int i = ; i <= n; ++i) {
int y = gr[r - ][i];
gr[r][i] = gr[r - ][y];
h[r][i] = Mul(h[r - ][i], h[r - ][y], y);
}
}
for (int a, b, x, y; nq--; ) {
scanf("%d%d%d%d", &x, &a, &y, &b);
if (dep[x] > dep[y]) {
swap(x, y), swap(a, b);
}
Mat ay(f[][y], f[][y]);
int v = y, fy = ;
for (int i = LG - ; ~i; --i) {
if (dep[gr[i][v]] >= dep[x]) {
if (fy) ay = h[i][v], fy = ; else ay = Mul(ay, h[i][v], v);
v = gr[i][v];
}
}
if (x == v) {
printf("%lld\n", ay.v[b][a] + g[a][x] > INF / ? - : ay.v[b][a] + g[a][x]);
continue;
}
Mat ax(f[][x], f[][x]);
int u = x, fx = ;
for (int i = LG - ; ~i; --i) {
if (gr[i][u] != gr[i][v]) {
if (fx) ax = h[i][u], fx = ; else ax = Mul(ax, h[i][u], u);
if (fy) ay = h[i][v], fy = ; else ay = Mul(ay, h[i][v], v);
u = gr[i][u], v = gr[i][v];
}
}
int lc = gr[][u];
LL t0 = g[][lc] + f[][lc] - f[][u] + ax.v[a][] - f[][v] + ay.v[b][];
LL t1 = g[][lc] + f[][lc] - min(f[][u], f[][u]) + min(ax.v[a][], ax.v[a][]) - min(f[][v], f[][v]) + min(ay.v[b][], ay.v[b][]);
printf("%lld\n", min(t0, t1) > INF / ? - : min(t0, t1));
}
return ;
}
【NOIP 2018】保卫王国(动态dp / 倍增)的更多相关文章
- luogu5024 [NOIp2018]保卫王国 (动态dp)
可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...
- 解题:NOIP 2018 保卫王国
题面 最小支配集=全集-最大独立集 所以先把点权改成正无穷/负无穷来保证强制选/不选某个点到独立集里,然后变成了洛谷的动态DP模板 GTMDNOIP2018ZTY #include<stack& ...
- JZOJ5966. [NOIP2018TGD2T3] 保卫王国 (动态DP做法)
题目大意 这还不是人尽皆知? 有一棵树, 每个节点放军队的代价是\(a_i\), 一条边连接的两个点至少有一个要放军队, 还有\(q\)次询问, 每次规定其中的两个一定需要/不可放置军队, 问这样修改 ...
- P5024 保卫王国(动态dp/整体dp/倍增dp)
做法(倍增) 最好写的一种 以下0为不选,1为选 \(f_{i,0/1}\)为\(i\)子树的最小值,\(g_{i,0/1}\)为除i子树外的最小值 \(fh_{i,j,0/1,0/1}\)为确定\( ...
- 【NOIP2018】保卫王国 动态dp
此题场上打了一个正确的$44pts$,接着看错题疯狂$rush$“正确”的$44pts$,后来没$rush$完没将之前的代码$copy$回去,直接变零分了..... 这一题我们显然有一种$O(nm)$ ...
- luoguP5024 保卫王国 动态dp
题目大意: emmmmm 题解: QAQ #include <cstdio> #include <cstring> #include <iostream> usin ...
- LuoguP5024 保卫王国(动态DP,LCT)
最小权覆盖集 = 全集 - 最大权独立集 强制取点.不取点可以使用把权值改成正无穷或负无穷实现 接下来就是经典的"动态最大权独立集"了 O(nlogn). 这不是我说的,是immo ...
- BZOJ 5466: [Noip2018]保卫王国 动态DP
Code: // luogu-judger-enable-o2 #include<bits/stdc++.h> #define ll long long #define lson (now ...
- 模版 动态 dp
模版 动态 dp 终于来写这个东西了.. LG 模版:给定 n 个点的数,点有点权, $ m $ 次修改点权,求修改完后这个树的最大独立集大小. 我们先来考虑朴素的最大独立集的 dp \[dp[u][ ...
随机推荐
- 软件设计、DDD概念及落地时的一些零碎思考和记录
DDD理解 DDD体现的是对现实的充分尊重. 1.尊重业务现实,领域专家.领域语言等概念 2.尊重团队现实 3.尊重变化 Application 对某一业务线的整体掌控,流程组装,进度管理,存储时机掌 ...
- 20155204《网络对抗》Exp7 网络欺诈防范
20155204<网络对抗>Exp7 网络欺诈防范 一.基础问题回答 1.通常在什么场景下容易受到DNS spoof攻击 在不安全的网络环境下访问网站. 2.在日常生活工作中如何防范以上两 ...
- 20155333 《网络对抗》 Exp5 MSF基础应用
20155333 <网络对抗> Exp5 MSF基础应用 基础问题回答 用自己的话解释什么是exploit,payload,encode exploit:攻击手段,是能使攻击武器(payl ...
- 通过定义过滤器filter解决跨域问题
跨域是比较常见问题,比较简单的方式就是直接定义一个过滤器filter,然后在请求头里面加上一些参数.下面来看看具体的写法吧. 一.java代码 package com.hj.usera ...
- 【php增删改查实例】 第二节 - MYSQL环境配置
安装好xampp后,会自带一个mysql,也就是说,正常情况下,你直接这样: 就可以启动mysql了. 如果你了,下面的步骤就别看了哈. if( 启动成功 ){ return; } 如果你的电脑上已经 ...
- 使用nginx很卡之strace命令
一.strace命令常用参数 strace -tt -T -v -f -e trace= -p -tt 在每行输出的前面,显示毫秒级别的时间 -T 显示每次系统调用所花费的时间 -v 对于某些相关调用 ...
- python面试题(四)
一.数据类型 1.字典 1.1 现有字典 dict={‘a’:24,‘g’:52,‘i’:12,‘k’:33}请按字典中的 value 值进行排序? sorted(dict.items(),key=l ...
- HTML 图像实例
61.插入图像本例演示如何在网页中显示图像.图像标签(<img>)和源属性(Src)在 HTML 中,图像由 <img> 标签定义. <img> 是空标签,意思是说 ...
- Unity程序协同问题,传送时屏幕变黑变亮的解决,常规操作的行为集合
在unity中运行某段程序时往往需要运行另外一段不相干但是却对功能上有需求的程序,比如进行场景传送,在传送点处,点击I键,屏幕慢慢变黑,场景传送到另外一个场景,场景又慢慢变亮.这里首先涉及两个物体,一 ...
- 做游戏的小伙伴们注意了,DDoS还可以这样破!
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯游戏云发表于云+社区专栏 作者:腾讯DDoS安全专家.腾讯云游戏安全专家haroldchen 摘要:在游戏出海的过程中,DDoS攻 ...