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\)拆了)

扫描二维码关注公众号,回复: 7428898 查看本文章

然后看这个式子找矩阵

\[\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做法,对每个节点重儿子开线段树轻儿子开线段树

猜你喜欢

转载自www.cnblogs.com/spbv587/p/11626352.html
ddp
T3