10.5 T3 DDP BZOJ 4712
10.5
T3(bzoj 4712) 毒瘤DDP
题面:
(光看题面就知道有多毒瘤)
就是说让你从一个子树内选一些点, 使得这些点能够把这个子树的所有叶子与子树的根分离开;
首先暴力很好想 , 也很好写, 就是一个树形DP的式子, 每次修改都\(DFS\)维护一遍, \(O(1)\)查询;
设\(f_x\)为以\(x\)为根的子树内的答案;
则有\(f_x=min(\sum f_{son}, val_x)\)
贴暴力代码:
void dfs(int x)//暴力
{
g[x] = 0;
if(du[x] == 1&&x != 1) g[x] = 2e9;
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x]) continue;
fa[i->to] = x;
dfs(i->to);
}
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x]) continue;
g[x] += f[i->to];
}
f[x] = min(g[x], w[x]);
}
然后就考虑怎么优化,跑不了要根据这个式子的
考虑把这棵树链剖, 剖完之后边就有了重链和轻边之分,点就有了重儿子 轻儿子之分;
然后刚刚的式子就可变形
\(f(x) = min(g(x)+f(son), val(x))\)
其中\(f\)和上面一样 \(g(x)\)指所有轻儿子的\(f\)值之和,\(f_{son}\)是重儿子(其实就是把刚刚的\(\sum\)拆了)
然后看这个式子找矩阵
\[\begin{pmatrix}0&f_v\end{pmatrix}$$ $*$ $$\begin{pmatrix}0&val_x\\\infty&g_x\end{pmatrix}$$ $=$ $$\begin{pmatrix}0&f_x\end{pmatrix}
\]
这里矩阵乘的定义需要改 由以前的相乘之后求和 改为相加之后取\(min\)(因为满足结合律a, 看看式子也知道)
然后就知道\(f_{son}\)(重儿子)通过乘上父亲节点的矩阵就可以转移到\(f_x\)
考虑询问:对每一个节点都维护一个转移矩阵,询问某一个点时直接从叶子乘到那个点就是ta的答案
原因:1 每个点都在重链上
2 每个重链都有叶子节点
3 每个叶子节点转移矩阵是\((0, val_x)=(0 , f_x)\)
然后考虑怎么乘,(当然不是暴力乘啦) 线段树啊, 重链上是连续的DFS序, 树剖基本操作啊
线段树上每个点也放矩阵不就行了吗, 每次找答案的时候就从线段树上扒矩阵。。。。
考虑修改:
显然改一个点的话对ta的子树是没有影响的,只会对父亲及祖先有影响(其实跟这个也没啥关系, 这是二分的做法,找第一个影响的点)
修改的话首先要在线段树上把它矩阵中的\(val\)改了,你会发现
1.对这条链上其他节点的矩阵是没有影响的(因为矩阵里存的\(val\)值和\(g\)值)所以它在本链上只是单点修改;
2. 它会对链顶的父亲的\(g\)产生影响,对链顶父亲也要单点修改;
3. 然后就是重复1.2直到根节点
4.考虑怎么修改链顶的父亲, ta的\(g\)是轻儿子的\(f\)组成的, 现在你链顶的\(f\)已经改了。。
所以要在改之前把链顶\(f\)记录下来, 让其减去旧的链顶\(f\)加上新的链顶\(f\) 即可
好像没了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 200005
#define int long long
using namespace std;
int read()
{
register int x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
int n, w[N], m, g[N], fa[N], du[N], f[N];
int rk[N], id[N], size[N], dep[N], son[N], top[N], bot[N], dfn;
struct edge
{
int to;
edge *nxt;
edge(int to, edge *nxt) : to(to), nxt(nxt) {}
} *head[N];
void add(int x, int y)
{
head[x] = new edge(y, head[x]);
du[x] ++;
}
void dfs(int x)//暴力
{
g[x] = 0;
if(du[x] == 1&&x != 1) g[x] = 2e9;
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x]) continue;
fa[i->to] = x;
dfs(i->to);
}
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x]) continue;
g[x] += f[i->to];
}
f[x] = min(g[x], w[x]);
}
struct matrix
{
int v[3][3];
friend matrix operator * (const matrix &a, const matrix &b)
{
matrix res;
for(int i = 1; i <= 2; i ++)
{
for(int j = 1; j <= 2; j ++)
{
res.v[i][j] = 1e17;
for(int k = 1; k <= 2; k ++)
{
res.v[i][j] = min(res.v[i][j], a.v[i][k] + b.v[k][j]);
}
}
}
return res;
}
}a[N];
void dfs1(int x)
{
size[x] = 1;
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x]) continue;
fa[i->to] = x;
dep[i->to] = dep[x] + 1;
dfs1(i->to);
size[x] += size[i->to];
if(size[i->to] > size[son[x]]) son[x] = i->to;
}
}
void dfs2(int x, int tp)
{
rk[x] = ++dfn;
id[dfn] = x;
top[x] = tp;
bot[tp] = x;
g[x] = 0;
if(du[x] == 1&&x != 1) g[x] = 2e9;
if(son[x]) dfs2(son[x], tp);
for(edge *i = head[x]; i; i = i->nxt)
{
if(i->to == fa[x] || i->to == son[x]) continue;
dfs2(i->to, i->to);
}
for(edge *i = head[x]; i ; i = i->nxt)
{
if(i->to == fa[x] || i->to == son[x]) continue;
g[x] += f[i->to];
}
f[x] = min(w[x], g[x] + f[son[x]]);
}
struct Segment
{
struct node
{
int l, r;
matrix v;
node *li, *ri;
node(int l, int r) : l(l), r(r) {
li = NULL, ri = NULL;
}
int mid() {
return (l + r) >> 1;
}
void up() {
v = ri->v * li->v;
}
}*root;
void build(node *&k, int l, int r)
{
k = new node(l, r);
if(l == r)
{
k->v = a[id[l]];
return ;
}
build(k->li, l, k->mid());
build(k->ri, k->mid()+1, r);
k->up();
}
void change(node *k , int pos)
{
if(k->l == k->r)
{
k->v = a[id[pos]];
return ;
}
if(pos <= k->mid()) change(k->li, pos);
else change(k->ri, pos);
k->up();
}
matrix ask(node *k, int l, int r)
{
if(l == k->l &&r == k->r)
{
return k->v;
}
if(r <= k->mid()) return ask(k->li, l, r);
else if(l >= k->mid() + 1) return ask(k->ri, l, r);
else return ask(k->ri, k->mid()+1, r) * ask(k->li, l, k->mid());
}
}A;
int query(int x)
{
return A.ask(A.root, rk[x], rk[bot[top[x]]]).v[1][2];//某点的f
}
void update(int x)
{
while(x) {
a[x].v[1][2] = w[x];
a[x].v[2][2] = g[x];//修改节点矩阵
A.change(A.root, rk[x]);//维护线段树
if(x == 1) break;
x = top[x];
g[fa[x]] -= f[x];//维护轻重链交替的地方
f[x] = A.ask(A.root, rk[x], rk[bot[top[x]]]).v[1][2];//找到那个地方的f值
g[fa[x]] += f[x];
x = fa[x];//跳到上面的重链
}
}
signed main()
{
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i ++)
w[i] = read();
for(int i = 1; i < n; i ++)
{
int x, y;
x = read(); y = read();
add(x, y); add(y, x);
}
fa[1] = 0;
dep[1] = 1;
dfs1(1);
dfs2(1, 1);
for(int i = 1; i <= n; i ++)//叶子节点矩阵会赋成val
{
a[i].v[1][1] = 0;
a[i].v[1][2] = w[i];
a[i].v[2][2] = g[i];
a[i].v[2][1] = 1e17;
}
A.build(A.root, 1, n);
m = read();
char s[5];
for(int i = 1, x, y; i <= m; i ++)
{
scanf("%s", s + 1);
if(s[1] == 'Q')
{
x = read();
printf("%lld\n",query(x));
}
else
{
x = read(); y = read();
w[x] += y;
update(x);//单点修改 更新节点矩阵
}
}
return 0;
}
/*
4
4 3 2 1
1 2
1 3
4 2
4
Q 1
Q 2
C 4 10
Q 1
*/
对于大部分ddp 来说
1.找出式子;
2.化成关于重儿子的式子;
3.推出矩阵, 放线段树里
4.修改时跳链;
这是矩阵优化后的DDP可做的;
如果有那种修改没有可减性(就是你不能减去原来的再加上现在的, 比如与, 或运算), 就要用原始的DDP做法,对每个节点重儿子开线段树轻儿子开线段树