题面

题目链接—Luogu

题目链接—Loj(要加Freopen)

题解

什么是动态DP?

OneInDark:你不需要知道这么多,你只需要知道是利用了广义矩阵乘法就够了!


广义矩乘

广义矩阵乘法,简单来说,就是把基本的 乘法加法 运算符改成其它运算符,同时这两种运算要满足 前者对后者有分配律,如:加法最大或最小值按位与异或 等。因为,我们会发现,乘法加法 组成的传统矩阵乘法之所以有哪些性质,其根本原因就在于乘法对加法的分配律。

举个例子,有这么个 DP 转移:

d

p

[

i

]

[

0

]

=

min

(

d

p

[

i

1

]

[

0

]

+

A

,

d

p

[

i

1

]

[

1

]

+

B

)

d

p

[

i

]

[

1

]

=

min

(

d

p

[

i

1

]

[

0

]

+

C

,

d

p

[

i

1

]

[

1

]

+

D

)

dp[i][0]=\min(dp[i-1][0]+A,dp[i-1][1]+B)\\ dp[i][1]=\min(dp[i-1][0]+C,dp[i-1][1]+D)

dp[i][0]=min(dp[i−1][0]+A,dp[i−1][1]+B)dp[i][1]=min(dp[i−1][0]+C,dp[i−1][1]+D)

那么它转化为 加法最小值 的广义矩阵乘法后,从

(

d

p

[

i

1

]

[

0

]

,

d

p

[

i

1

]

[

1

]

)

\big(dp[i-1][0],dp[i-1][1]\big)

(dp[i−1][0],dp[i−1][1]) 到

(

d

p

[

i

]

[

0

]

,

d

p

[

i

]

[

1

]

)

\big(dp[i][0],dp[i][1]\big)

(dp[i][0],dp[i][1]) 的转移矩阵就是

(

A

C

B

D

)

\left( \begin{matrix} A&C\\ B&D\\ \end{matrix} \right)

(AB​CD​)

顺便提一下此时的单位矩阵:

(

0

+

+

0

)

\left( \begin{matrix} 0&+\infty\\ +\infty&0\\ \end{matrix} \right)

(0+∞​+∞0​)

0

0

0 表示直接转移过来,不改变原值的基础上参与最小值运算,与传统矩乘的

1

1

1 的作用相同。

+

+\infty

+∞ 在求最小值前提下,对结果没有影响,与传统矩乘的

0

0

0 作用相同。

广义矩阵乘法的实现很简单,只需要把传统矩乘的模板

c

o

p

y

\tt copy

copy 过来,然后把原来计算

C

i

,

j

=

k

A

i

,

k

B

k

,

j

C_{i,j}=\sum_{k}A_{i,k}\cdot B_{k,j}

Ci,j​=k∑​Ai,k​⋅Bk,j​

的部分:for(...k...) C[i][j]=C[i][j]+A[i][k]*B[k][j](以加法和最小值为例)改成:

C

i

,

j

=

min

k

{

A

i

,

k

+

B

k

,

j

}

C_{i,j}=\min_{k}\{A_{i,k}+B_{k,j}\}

Ci,j​=kmin​{Ai,k​+Bk,j​}

for(...k...) C[i][j]=min(C[i][j],A[i][k]*B[k][j]),就完了。


树剖+动态 DP

其实这道题严格意义上没有真正的修改,所以打树剖难免有点大材小用。

不过,这都是为了配合动态 DP 啊!

我们令

g

(

i

,

0

)

g(i,0)

g(i,0) 为不在

i

i

i 结点驻兵时,

i

i

i 的非重儿子子树的最小花费,

g

(

i

,

1

)

g(i,1)

g(i,1) 为

i

i

i 结点驻兵时,

i

i

i 的非重儿子子树的最小花费。在进行一次修改时,只会变动最多

log

n

\log n

logn 个点的

g

g

g 值,因此可以直接更新它。

同时令

f

(

i

,

0

)

f(i,0)

f(i,0) 为不在

i

i

i 结点驻兵时,

i

i

i 的子树(包括

i

i

i)的最小花费,令

f

(

i

,

1

)

f(i,1)

f(i,1) 为在

i

i

i 结点驻兵时,

i

i

i 的子树(包括

i

i

i)的最小花费。可以得到转移方程:

g

(

i

,

0

)

=

y

i

f

(

y

,

1

)

g

(

i

,

1

)

=

y

i

min

{

f

(

y

,

0

)

,

f

(

y

,

1

)

}

f

(

i

,

0

)

=

f

(

S

O

N

i

,

1

)

+

g

(

i

,

0

)

f

(

i

,

1

)

=

min

{

f

(

S

O

N

i

,

0

)

,

f

(

S

O

N

i

,

1

)

}

+

g

(

i

,

1

)

+

p

i

g(i,0)=\sum_{y\in i\,的轻儿子} f(y,1)~~~~~~~~~~~~~~~~~~~~~~~\\~\\ g(i,1)=\sum_{y\in i\,的轻儿子} \min\{f(y,0),f(y,1)\}\\~\\ f(i,0)=f(SON_i,1)+g(i,0)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\\~\\ f(i,1)=\min\{f(SON_i,0),f(SON_i,1)\}+g(i,1)+p_i

g(i,0)=y∈i的轻儿子∑​f(y,1)                        g(i,1)=y∈i的轻儿子∑​min{f(y,0),f(y,1)} f(i,0)=f(SONi​,1)+g(i,0)                                         f(i,1)=min{f(SONi​,0),f(SONi​,1)}+g(i,1)+pi​

我们可以就此得出

(

f

(

S

O

N

i

,

0

)

,

f

(

S

O

N

i

,

1

)

)

\big(f(SON_i,0),f(SON_i,1)\big)

(f(SONi​,0),f(SONi​,1)) 到

(

f

(

i

,

0

)

,

f

(

i

,

1

)

)

\big(f(i,0),f(i,1)\big)

(f(i,0),f(i,1)) 的转移矩阵,用 加法最小值 的广义矩乘:

(

+

g

(

i

,

1

)

+

p

i

g

(

i

,

0

)

g

(

i

,

1

)

+

p

i

)

\left( \begin{matrix} +\infty&g(i,1)+p_i\\ g(i,0)&g(i,1)+p_i \end{matrix} \right)

(+∞g(i,0)​g(i,1)+pi​g(i,1)+pi​​)

那么,我们就可以用线段树维护区间矩乘了。把每个点独特的转移矩阵都经树链剖分放到某条重链上,一个点

i

i

i 的向量矩阵

(

f

(

i

,

0

)

,

f

(

i

,

1

)

)

\big(f(i,0),f(i,1)\big)

(f(i,0),f(i,1)) 就等于

(

0

,

0

)

×

Q

u

e

r

y

(

d

f

n

i

,

)

\big(0,0\big)\times {\rm Query}({\rm dfn}_i~,~重链底)

(0,0)×Query(dfni​ , 重链底)

修改一个点的时候,只会对 到根的途径上所有重链顶的父亲

g

g

g 值以及转移矩阵有改变。对

g

g

g 的改变没有必要重新把轻儿子枚举一边,只需要把变动的那个重链顶的贡献变化量加上去。为此,有必要把每个重链顶先前的

f

f

f 值都存下来。

针对 必须驻兵 和 必须不驻兵 两种限制,可以分别把节点的

p

i

p_i

pi​ 加上

-\infty

−∞ 和

+

+\infty

+∞ 来解决。这都是老套路了。

时间复杂度

O

(

n

log

2

n

)

\rm O(n\log^{_2}n)

O(nlog2​n) 。

CODE

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
struct mat{
int n,m;
LL s[2][2];
mat(){n=m=0;}
void set(int N,int M) {
n=N;m=M;
for(int i = 0;i < n;i ++) {
for(int j = 0;j < m;j ++) {
s[i][j] = 1e18;
}
}
}
}A,B;
mat operator * (mat a,mat b) {
mat c; c.set(a.n,b.m);
for(int i = 0;i < a.n;i ++) {
for(int k = 0;k < a.m;k ++) {
for(int j = 0;j < b.m;j ++) {
c.s[i][j] = min(c.s[i][j],a.s[i][k] + b.s[k][j]);
}
}
}return c;
}
mat qkpow(mat a,LL b) {
mat res; res.set(a.n,a.m);
for(int i = 0;i < a.n;i ++) res.s[i][i] = 0;
while(b > 0) {
if(b & 1) res = res * a;
a = a * a; b >>= 1;
}
return res;
}
mat tre[MAXN<<2];
int M;
void maketree(int n) {
M = 1;while(M < n+2) M <<= 1;
for(int i = 1;i <= M*2;i ++) tre[i].set(2,2);
}
void addtree(int x,mat y) {
int s = M + x; tre[s] = y; s >>= 1;
while(s) tre[s] = tre[s<<1|1] * tre[s<<1],s >>= 1;
}
mat findtree(int l,int r) {
int s = M+l-1,t = M+r+1;
mat ls,rs;ls.set(2,2);
ls.s[0][0] = ls.s[1][1] = 0;
rs = ls;
while(s || t) {
if((s>>1) ^ (t>>1)) {
if(!(s&1)) ls = tre[s^1] * ls;
if(t & 1) rs = rs * tre[t^1];
}else break;
s >>= 1;t >>= 1;
}
return rs * ls;
}
//--------------------------------------------------------------------
vector<int> g[MAXN];
LL pi[MAXN];
int fa[MAXN];
int d[MAXN],siz[MAXN],son[MAXN];
int dfn[MAXN],tp[MAXN],tim,tl[MAXN];
LL G[MAXN][2];
void dfs0(int x,int ff) { // fa[], d[], siz[], son[],
d[x] = d[fa[x] = ff] + 1;
siz[x] = 1; son[x] = 0; tim = 0;
for(int i = 0,le=(int)g[x].size();i < le;i ++) {
int y = g[x][i];
if(y != ff) {
dfs0(y,x);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) son[x] = y;
}
}
return ;
}
LL tf[MAXN][2];
mat tran[MAXN];
void dfs(int x,int ff) { // dfn[], tp[], tl[]
if(son[ff] == x) tp[x] = tp[ff];
else tp[x] = x;
dfn[x] = ++ tim;
tl[tp[x]] = max(tl[tp[x]],dfn[x]);
G[x][0] = G[x][1] = 0;
tf[x][0] = 0;tf[x][1] = pi[x]; if(son[x]) dfs(son[x],x);
tf[x][0] += tf[son[x]][1];
tf[x][1] += min(tf[son[x]][0],tf[son[x]][1]);
for(int i = 0,le=(int)g[x].size();i < le;i ++) {
int y = g[x][i];
if(y != ff && y != son[x]) {
dfs(y,x);
G[x][0] += tf[y][1];
G[x][1] += min(tf[y][1],tf[y][0]);
}
}
tf[x][0] += G[x][0]; tf[x][1] += G[x][1];
mat tm; tm.set(2,2);
tm.s[0][0] = 1e18; tm.s[0][1] = G[x][1]+pi[x];
tm.s[1][0] = G[x][0]; tm.s[1][1] = G[x][1]+pi[x];
addtree(dfn[x],tran[x] = tm);
return ;
}
void addline(int a,LL pa) {
pi[a] = pa;
while(a) {
mat tm = tran[a];
tm.s[0][1] = tm.s[1][1] = G[a][1]+pi[a];
tm.s[1][0] = G[a][0];
addtree(dfn[a],tm);
int hd = tp[a];
mat tmp;tmp.set(1,2);tmp.s[0][0]=tmp.s[0][1]=0;
tmp = tmp * findtree(dfn[hd],tl[hd]);
LL F[2] = {tmp.s[0][0],tmp.s[0][1]};
a = fa[hd];
if(a) {
G[a][0] += F[1] - tf[hd][1];
G[a][1] += min(F[0],F[1]) - min(tf[hd][0],tf[hd][1]);
}
tf[hd][0] = F[0]; tf[hd][1] = F[1];
}return ;
}
int main() {
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
n = read(); m = read(); read();
for(int i = 1;i <= n;i ++) {
pi[i] = read();
}
for(int i = 1;i < n;i ++) {
s = read();o = read();
g[s].push_back(o);
g[o].push_back(s);
}
dfs0(1,0);
maketree(n);
dfs(1,0);
while(m --) {
int a = read(),xx = read(),b = read(),yy = read();
int pa = pi[a],pb = pi[b];
LL nma = pa + (xx ? -1e13:1e13);
LL nmb = pb + (yy ? -1e13:1e13);
addline(a,nma); addline(b,nmb);
LL as = min(tf[1][0],tf[1][1]);
if(xx) as += 1e13;
if(yy) as += 1e13;
if(as > (LL)1e10) printf("-1\n");
else printf("%lld\n",as);
addline(a,pa); addline(b,pb);
}
return 0;
}
/*
[f(i,0),f(i,1)] --- [0,0] [oo ,g(i,1)+pi]
[g(i,0),g(i,1)+pi]
*/

养成习惯,写矩乘时从 0 开始编号。节省空间能快很多。如果过程中越界可能性大,那就算了吧

[NOIP2018提高组] 保卫王国 (树链剖分+动态DP)的更多相关文章

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

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

  2. P5024 [NOIP2018 提高组] 保卫王国

    思路: 首先想到每次询问两个点后就从这两个点开始往上爬,沿路更新 dp 值即可. #include <bits/stdc++.h> #define For(i,a,b) for(int i ...

  3. 【bzoj4999】This Problem Is Too Simple! 树链剖分+动态开点线段树

    题目描述 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x<2^31) ...

  4. [bzoj 3531][SDOI2014]旅行(树链剖分+动态开点线段树)

    题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3531 分析: 对于每个颜色(颜色<=10^5)都建立一颗线段树 什么!那么不是M ...

  5. (中等) HDU 5293 Tree chain problem,树链剖分+树形DP。

    Problem Description   Coco has a tree, whose vertices are conveniently labeled by 1,2,…,n.There are ...

  6. 洛谷P3313 [SDOI2014]旅行(树链剖分 动态开节点线段树)

    题意 题目链接 Sol 树链剖分板子 + 动态开节点线段树板子 #include<bits/stdc++.h> #define Pair pair<int, int> #def ...

  7. 2018.07.24 bzoj3531: [Sdoi2014]旅行(树链剖分+动态开点)

    传送门 树链剖分. 如何维护? 如果颜色少直接每种颜色一颗线段树走人. 但这题颜色数量不大于1e5" role="presentation" style="po ...

  8. BZOJ 3531 [Sdoi2014]旅行 树链剖分+动态开点线段树

    题意 S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神教.隐形独角兽教.绝地教都是常见的信仰. 为了方便,我们用 ...

  9. Codeforces 856D - Masha and Cactus(树链剖分优化 dp)

    题面传送门 题意: 给你一棵 \(n\) 个顶点的树和 \(m\) 条带权值的附加边 你要选择一些附加边加入原树中使其成为一个仙人掌(每个点最多属于 \(1\) 个简单环) 求你选择的附加边权值之和的 ...

随机推荐

  1. java通过socket实现https get 请求网页

    1. 首先要初始化ssl context SSLContext context = SSLContext.getDefault(); socket = (SSLSocket) context.getS ...

  2. 000 上传本地库到Github远程库过程全记录

    20220613 Github上新创建了一个CsImage库,之后本地创建了一个对应名称的目录,并创建本地库,进行了上传操作,记录一下过程 1.Github上CsImage库创建完成 Github上创 ...

  3. 开发工具-Visual Studio / Visual Studio Code 官方下载地址

    更新记录 2022年6月10日 完善标题. Visual Studio官方下载地址 https://visualstudio.microsoft.com/ Visual Studio Code官方下载 ...

  4. 浅析DispatchProxy动态代理AOP

    浅析DispatchProxy动态代理AOP(代码源码) 最近学习了一段时间Java,了解到Java实现动态代理AOP主要分为两种方式JDK.CGLIB,我之前使用NET实现AOP切面编程,会用Fil ...

  5. 详解PHP如何实现斐波那契数列的简单实例

    文章来自:有解网 http://www.youjieweb.com/original/index/articleId/64.html 使用场景: 面试 本文讲的是如何用php实现PHP实现斐波那契数列 ...

  6. C# 读写文件从用户态切到内核态,到底是个什么流程?

    一:背景 1. 一个很好奇的问题 我们在学习 C# 的过程中,总会听到一个词叫做 内核态 ,比如说用 C# 读写文件,会涉及到代码从 用户态 到 内核态 的切换,用 HttpClient 获取远端的数 ...

  7. 在mybatis中使用sum函数返回对象为Null

    首先大家看一下我的XML中的SQL .DAO  和实体对象 XML DAO PO 乍一看 没毛病. 但是在Mybatis中使用sum函数,如果返回值是0(就是你在Navicat中运行的的sql正常,结 ...

  8. 攻防世界MISC进阶区—48-51

    48.Become_a_Rockstar 得到无类型文件,010 Editor打开为几段话,看到标示性的NCTF{),怀疑是用脚本加密后的结果,网上查了一下,得知Rockstar是一种语言,用rock ...

  9. Josephus问题(Ⅱ)

    题目描述 n个人排成一圈,按顺时针方向依次编号1,2,3-n.从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子.这样不断循环下去,圈子里的人将不断减少.最终一定会剩下一个人 ...

  10. docker部署练习

    三个部署任务 docker部署nginx docker pull nginx #拉取nginx镜像 docker images #检查拉取的镜像 docker run -d -p 3344:80 -- ...