题目大意

这还不是人尽皆知?

有一棵树, 每个节点放军队的代价是\(a_i\), 一条边连接的两个点至少有一个要放军队, 还有\(q\)次询问, 每次规定其中的两个一定需要/不可放置军队, 问这样修改以后的最小代价.

解题思路

考虑一个朴素的DP, 设\(f_{x,0/1}\)表示这个点选/不选的最小代价. 显然有

\[f_{x,0}=\sum f_{y,1}
\]
\[f_{x,1}=a_x+\sum \min(f_{y,0}, f_{y,1})
\]

其中\(y\)是\(x\)的儿子. 最后的答案显然是\(min(f_{1,0}, f_{1,1})\)(1是根)

注意到我们每一次修改以后, 影响到的是从这个点到根节点的一条链. 我们需要快速修改这一段的\(f\)值. 怎么样可以使这个操作的时间复杂度变得正确一点呢? 显然树剖吧.

那就剖咯. 对轻重儿子区别对待. 设\(g_{x,0/1}\)表示\(x\)这个点选/不选, 除了重儿子外的最小代价. 显然有

\[g_{x,0}=\sum_{y \ne son_x} f_{y,1}
\]
\[g_{x,1}=a_x+\sum_{y \ne son_x} \min(f_{y,0}, f_{y,1})
\]

只不过是少了一个重儿子而已

那么\(f\)可以借助\(g\)转移过来.

\[f_{x,0}=f_{son,1}+g_{x,0}
\]
\[f_{x,1}=\min(f_{son,0}, f_{son,1})+g_{x,1}
\]

问题来了. 怎么维护一条链的信息?

考虑重新定义矩阵乘法, 若\(AB=C\), 则

\[C_{i,j}=\min_{k}(A_{i,k}+B_{k,j})
\]

于是转移方程可以写成如下

\[\begin{bmatrix}
+\infty & g_{x,0}\\
g_{x,1} & g_{x,1}
\end{bmatrix}

\begin{bmatrix}
f_{son,0}\\
f_{son,1}
\end{bmatrix}

=

\begin{bmatrix}
f_{x,0} \\
f_{x,1}
\end{bmatrix}
\]

在每一条链上维护\(g\)所在矩阵的乘积. 把一个询问按照重链分解成几条从当前点到根节点的链即可. 考虑当我们限制一个点强制选/不选后, 只需要把\(g\)数组对应的值赋成\(+\infty\)即可.

后记

其实一次有两个被限制了, 就是在暗示我们除了动态DP还有更简单的联赛做法.

但是我太懒了, 而且没做过树链剖分的题, 所以试一下(没错蒟蒻的第一个树链剖分就是做动态DP)

(可能是本人写过最长的Code?)

#include <cstdio>
#include <cstring>
#define N 100010
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3fll
#define init(a, b) memset(a, b, sizeof(a))
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
//#pragma GCC optimize(2)
using namespace std;
inline int read() // notice : 1. long long ? 2. negative ?
{
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x;
}
inline ll min(ll a, ll b){return a < b ? a : b;}
struct Matrix
{
ll a[2][2];
inline Matrix(){init(a, 0x3f);}
inline Matrix(int){a[0][0] = a[1][1] = 0; a[0][1] = a[1][0] = INF;}
inline Matrix(ll g0, ll g1){a[0][0] = INF, a[0][1] = g0, a[1][0] = a[1][1] = g1;}
inline Matrix operator*(const Matrix &b)
{
Matrix ret;
fo(i, 0, 1) fo(j, 0, 1) fo(k, 0, 1) ret.a[i][j] = min(ret.a[i][j], a[i][k] + b.a[k][j]);
return ret;
}
}tr[N << 2];
int n, q, ls[N << 2], rs[N << 2], rt[N], top[N], fa[N], sz[N], son[N], dfn[N], end[N], a[N], to[N << 1], pre[N << 1], last[N];
ll f[N][2], g[N][2];
#define mid ((l + r) >> 1)
void build(int &t, int l, int r)
{
static int tot = 0;
!t && (t = ++tot);
if(l == r) return ;
build(ls[t], l, mid); build(rs[t], mid + 1, r);
}
void insert(int t, int l, int r, Matrix v, int w)
{
if(l == r) return (void)(tr[t] = v, 0);
w <= mid ? insert(ls[t], l, mid, v, w) : insert(rs[t], mid + 1, r, v, w);
tr[t] = tr[ls[t]] * tr[rs[t]];
}
Matrix query(int t, int l, int r, int fl, int fr)
{
if(fl <= l && r <= fr) return tr[t];
Matrix ret(1);
fl <= mid && (ret = ret * query(ls[t], l, mid, fl, fr), 1);
fr > mid && (ret = ret * query(rs[t], mid + 1, r, fl, fr), 1);
return ret;
} inline void add(int u, int v){static int tot = 0; to[++tot] = v, pre[tot] = last[u], last[u] = tot;}
inline void dfs1(int u)
{
sz[u] = 1, f[u][1] = a[u];
for(int i = last[u]; i; i = pre[i])
if(to[i] ^ fa[u])
{
int v = to[i];
fa[v] = u; dfs1(v), sz[u] += sz[v];
f[u][0] += f[v][1], f[u][1] += min(f[v][0], f[v][1]);
sz[v] > sz[son[u]] && (son[u] = v);
}
}
inline void dfs2(int u)
{
static int tot = 0;
dfn[u] = ++tot, g[u][1] = a[u];
if(son[u]) top[son[u]] = top[u], dfs2(son[u]), end[u] = end[son[u]];
else end[u] = u;
for(int i = last[u]; i; i = pre[i])
if(to[i] ^ fa[u] && to[i] ^ son[u])
{
int v = to[i];
top[v] = v;
g[u][0] += f[v][1], g[u][1] += min(f[v][0], f[v][1]);
dfs2(v);
}
}
inline void update(int x)
{
for(; x; x = fa[x])
{
insert(rt[top[x]], dfn[top[x]], dfn[end[x]], Matrix(g[x][0], g[x][1]), dfn[x]);
Matrix p = tr[rt[x = top[x]]];
// printf("f[%d] : %lld %lld\n", x, f[x][0], f[x][1]);
g[fa[x]][0] -= f[x][1], g[fa[x]][1] -= min(f[x][0], f[x][1]);
f[x][0] = min(p.a[0][0], p.a[0][1]), f[x][1] = min(p.a[1][0], p.a[1][1]);
g[fa[x]][0] += f[x][1], g[fa[x]][1] += min(f[x][0], f[x][1]);
}
}
int main()
{
freopen("defense.in", "r", stdin);
freopen("defense.out", "w", stdout);
n = read(), q = read(); scanf(" %*s");
fo(i, 1, n) a[i] = read();
int u, v, x, y; ll t1, t2;
fo(i, 2, n) u = read(), v = read(), add(u, v), add(v, u);
top[1] = 1;
dfs1(1);
dfs2(1);
fo(i, 1, n) if(top[i] == i) build(rt[i], dfn[i], dfn[end[i]]);
fo(i, 1, n) insert(rt[top[i]], dfn[top[i]], dfn[end[i]], Matrix(g[i][0], g[i][1]), dfn[i]);
fo(i, 1, q)
{
u = read(), x = read() ^ 1, v = read(), y = read() ^ 1;
t1 = g[u][x], t2 = g[v][y];
g[u][x] = INF, update(u), g[v][y] = INF, update(v);
ll ans = min(f[1][0], f[1][1]);
printf("%lld\n", ans < INF ? ans : -1);
g[u][x] = t1, update(u), g[v][y] = t2, update(v); }
return 0;
}

JZOJ5966. [NOIP2018TGD2T3] 保卫王国 (动态DP做法)的更多相关文章

  1. luogu5024 [NOIp2018]保卫王国 (动态dp)

    可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...

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

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

  3. luoguP5024 保卫王国 动态dp

    题目大意: emmmmm 题解: QAQ #include <cstdio> #include <cstring> #include <iostream> usin ...

  4. P5024 保卫王国(动态dp/整体dp/倍增dp)

    做法(倍增) 最好写的一种 以下0为不选,1为选 \(f_{i,0/1}\)为\(i\)子树的最小值,\(g_{i,0/1}\)为除i子树外的最小值 \(fh_{i,j,0/1,0/1}\)为确定\( ...

  5. LuoguP5024 保卫王国(动态DP,LCT)

    最小权覆盖集 = 全集 - 最大权独立集 强制取点.不取点可以使用把权值改成正无穷或负无穷实现 接下来就是经典的"动态最大权独立集"了 O(nlogn). 这不是我说的,是immo ...

  6. BZOJ 5466: [Noip2018]保卫王国 动态DP

    Code: // luogu-judger-enable-o2 #include<bits/stdc++.h> #define ll long long #define lson (now ...

  7. P5024 保卫王国[倍增+dp]

    窝当然不会ddp啦,要写这题当然是考虑优化裸dp啦,但是这题非常麻烦,于是变成了黑题. 首先,这个是没有上司的舞会模型,求图的带权最大独立集. 不考虑国王的限制条件,有 \[ dp[x][0]+=dp ...

  8. Uoj 441 保卫王国

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

  9. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

随机推荐

  1. Oracle trunc和round的区别

    1.关于trunc 和round函数比较 整体概括: round函数 四舍五入trunc函数 直接截取 对于时间: Round函数对日期进行"四舍五入",Trunc函数对日期进行截 ...

  2. spring注解-web

    以往进行web项目开发都需要在web.xml配置servlet.filter.listener,在Servlet3.0可以通过注解的方式配置它们(注意:必须用tomcat7以上版本) @WebServ ...

  3. Mybatis读取数据实战

    1.Mybatis基础配置 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configu ...

  4. MemoryCache 如何清除全部缓存?

    最近有个需求需要定时清理服务器上所有的缓存.本来以为很简单的调用一下 MemoryCache.Clear 方法就完事了.谁知道 MemoryCache 类以及 IMemoryCache 扩展方法都没有 ...

  5. Linux 磁盘分区和挂载

    目录 Linux 磁盘分区和挂载 windows 下的分区 磁盘管理 相关命令 分区及挂载实现步骤 添加硬盘 分区步骤 步骤 挂载步骤 卸载分区步骤 补充: Linux 磁盘分区和挂载 windows ...

  6. 【LeetCode】835. Image Overlap 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  7. 【LeetCode】462. Minimum Moves to Equal Array Elements II 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:排序 方法二:直接找中位数 日期 题目地址: ...

  8. 【LeetCode】893. Groups of Special-Equivalent Strings 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  9. 【JAVA今法修真】 第七章 洞天风云起,索引混乱平

    您好,我是南橘,万法仙门的掌门,刚刚从九州世界穿越到地球,因为时空乱流的影响导致我的法力全失,现在不得不通过这个平台向广大修真天才们借去力量.你们的每一个点赞,每一个关注都是让我回到九州世界的助力,兄 ...

  10. C++中常用的数学函数总结

    我们在C++程序设计的过程中往往会使用到一些数学函数,那么不同的数学运算要用到什么函数哪?大家可以参考我的总结如下: 首先引用到数学函数时一定要记得加函数头文件 #include<cmath&g ...