DDP(动态 dp),是通过将状态改写成矩阵,然后定义具有结合律的与转移等价的运算,用数据结构快速维护的一种方法。在 NOIP2018 时作为 Day2 T3 出现。
NOIP2018 Day2T3 - 保卫王国
考场上根本不会...那个时候我是连暴力 dp 都不会的菜鸡,现在回来学一学。
我们来找一道简单的题目入手:给定一棵树,点有点权,每次可以修改一个点的权值,每次修改后求这棵树的最大独立集。$n \leq 10^5$
如果这个问题是静态的,我们可以设出状态 $f_{i,0/1}$ ,表示以 $i$ 为根的子树内, $i$ 这个点是否选择时的最大权值和。转移显然:
$$f_{i,0} = \sum_{v \in son_i} max\{f_{v,0},f_{v,1}\}$$
$$f_{i,1} = \sum_{v \in son_i} f_{v,0}$$
我们观察在 dfs 中该 dp 的转移方式:我们发现这类 dp 对儿子的转移顺序 没有要求,所以我们可以先从轻儿子转移,再从重儿子转移。我们将这棵树重链剖分后,对于重链上的每一个点,我们记录从它所有轻儿子转移来的最好状态 $lf_{v,0/1}$,转移和上面的差不多,只需要限制不从重儿子转移就可以了。
于是假设我们先处理处理出 $lf$,然后观察 $lf$ 和 $f$ 之间的关系:发现如果想求重链上每一个点的 f 值,我们只需要将这个链抽出来跑序列 dp 就可以了,我们记重儿子为 $hson$,转移如下:
$$f_{i,0} = lf_{i,0}+max\{f_{hson,0},f_{hson,1}\}$$
$$f_{i,1} = lf_{i,1}+f_{hson,0}$$
于是我们对于链上的一段查询就变成了彻彻底底的区间查询了。我们考虑在修改一个点的时候,如果能快速维护对序列 dp 的修改操作,我们在这个点所在重链所在的线段树上进行修改操作,然后调到链的 $fa$ 上继续修改,就可以在 $O(log^2n)$ 的时间复杂度内完成这一操作。
现在问题就在于如何快速维护这一操作了。我们在维护一般的有加油乘的 dp 时,我们都是用矩阵乘法,这次我们考虑定义矩阵的广义乘法运算:
$$C_{i,j} = \max_{k=1}^n\{A_{i,k},B_{k,j}\}$$
我们发现这个运算具有结合律,所以可以在线段树上维护。
所以我们考虑这个简单的问题的转移写到矩阵上应该是:

于是树剖后用线段树维护广义矩阵乘,就可以在 $O(nlog^2n)$ 的时间复杂度内做出本题。如果要追求 $O(nlogn)$ 的时间复杂度,可以使用 LCT 或者是全局平衡树来维护(虽然我都不会 zbl)。
贴一下代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1e5 + 5;
int a[MAXN],n,m;
#define lc (x<<1)
#define rc (x<<1|1)
struct Matrix{
    LL a2;
    Matrix(){
        //FOR(i,0,1) FOR(j,0,1) ai = INT_MIN;
        CLR(a,0);
    }

    Matrix operator * (const Matrix &t) const {
        Matrix res;
        FOR(i,0,1){
            FOR(j,0,1){
                FOR(k,0,1) res.ai = std::max(res.ai,ai+t.ak);
            }
        }
        return res;
    }
}last,newm,val[MAXN],sm[MAXN<<2];

std::vector<int> G[MAXN];
int fa[MAXN],sz[MAXN],dep[MAXN],dfn[MAXN],edfn[MAXN],son[MAXN],tp[MAXN],id[MAXN];

inline void dfs1(int v,int faa=0){
    sz[v] = 1;fa[v] = faa;
    for(auto x:G[v]){
        if(x == faa) continue;
        dep[x] = dep[v] + 1;dfs1(x,v);
        sz[v] += sz[x];
        if(!son[v] || sz[son[v]] < sz[x]) son[v] = x;
    }
}

inline void dfs2(int v,int t=1,int fa=0){
    static int ts = 0;dfn[v] = ++ts;id[ts] = v;
    edfn[t] = ts;tp[v] = t;
    // if(v == son[fa]) tp[v] = t;
    // else tp[v] = v;
    if(son[v]) dfs2(son[v],t,v);
    for(auto x:G[v]){
        if(x == fa || x == son[v]) continue;
        dfs2(x,x,v);
    }
}

inline void split(int rt){
    dep[1] = 1;dfs1(rt);dfs2(rt);
}

int lfMAXN,fMAXN;

inline void dfs3(int v,int fa=0){
    lfv = a[v];
    for(auto x:G[v]){
        if(x == fa || x == son[v]) continue;
        dfs3(x,v);
        // upd lf
        lfv += std::max(fx,fx);
        lfv += fx;
    }
    fv += lfv;fv += lfv;
    if(!son[v]) return;
    dfs3(son[v],v);
    fv += std::max(f[son[v]][0],f[son[v]][1]);
    fv += f[son[v]][0];
}

inline void pushup(int x){
    sm[x] = sm[lc]*sm[rc];
}

inline void build(int x,int l,int r){
    if(l == r){
        val[id[l]].a0 = lf[id[l]][0];val[id[l]].a0 = lf[id[l]][0];
        val[id[l]].a1 = lf[id[l]][1];val[id[l]].a1 = -1e18;
        sm[x] = val[id[l]];
        return;
    }
    int mid = (l + r) >> 1;
    build(lc,l,mid);build(rc,mid+1,r);
    pushup(x);
}

inline Matrix query(int x,int l,int r,int L,int R){
    if(l == L && r == R) return sm[x];
    int mid = (l + r) >> 1;
    if(R <= mid) return query(lc,l,mid,L,R);
    if(L > mid) return query(rc,mid+1,r,L,R);
    return query(lc,l,mid,L,mid)*query(rc,mid+1,r,mid+1,R);
}

inline void update(int x,int l,int r,int pos){
    if(l == r && l == pos){
        sm[x] = val[id[pos]];return;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(lc,l,mid,pos);
    else update(rc,mid+1,r,pos);
    pushup(x);
}

inline void change(int v,int w){
    val[v].a1 += w - a[v];a[v] = w;
    while(v){
        int t = tp[v];
        last = query(1,1,n,dfn[t],edfn[t]);
        update(1,1,n,dfn[v]);
        newm = query(1,1,n,dfn[t],edfn[t]);
        v = fa[t];
        val[v].a0 += std::max(newm.a0,newm.a1)-std::max(last.a0,last.a1);
        val[v].a0 = val[v].a0;
        val[v].a1 += newm.a0 - last.a0;
    }
}

signed main(){
    scanf("%lld%lld",&n,&m);
    FOR(i,1,n) scanf("%lld",a+i);
    FOR(i,1,n-1){
        int u,v;scanf("%lld%lld",&u,&v);
        G[u].pb(v);G[v].pb(u);
    }
    split(1);dfs3(1);build(1,1,n);
    FOR(i,1,m){
        int x,y;scanf("%lld%lld",&x,&y);change(x,y);
        Matrix ans = query(1,1,n,dfn[1],edfn[1]);
        printf("%lldn",std::max(ans.a0,ans.a1));
    }
    return 0;
}

<h2>关于保卫王国</h2>

首先答案显然是全集-最大独立集,所以变成了最大独立集,每次钦定一些点是否选的问题。
我们可以让强制选的点设为一个极大值,最后在答案里减去就可以了。对于强制不选的点我们可以设为一个极小值,这样选了这个点一定不优就不会选。由于树剖常数比较大,所以我写了个 fread(
附代码(基本上差不多):

#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

inline char nc(){
    #define SIZE 100000+5
    static char buf[SIZE],p1 = buf+SIZE,p2 = buf+SIZE;
    if(p1 == p2){
        p1 = buf;p2 = buf+fread(buf,1,SIZE,stdin);
        if(p1 == p2) return -1;
    }
    return *p1++;
}

inline void read(int &x){
    x = 0;char ch = nc();
    while(!isdigit(ch)) ch = nc();
    while(isdigit(ch)){
        x = (x<<1) + (x<<3) + (ch^'0');
        ch = nc();
    }
}

inline void read(LL &x){
    x = 0;char ch = nc();
    while(!isdigit(ch)) ch = nc();
    while(isdigit(ch)){
        x = (x<<1) + (x<<3) + (ch^'0');
        ch = nc();
    }
}

const int MAXN = 100000+5;
const LL MIN = -1e15;
std::vector<int> G[MAXN];
int n,m;LL a[MAXN];

#define lc (x<<1)
#define rc (x<<1|1)

struct Matrix{
    LL a2;
    Matrix(){CLR(a,0);}

    inline Matrix operator * (const Matrix &t) const {
        Matrix res;
        FOR(i,0,1){
            FOR(j,0,1){
                FOR(k,0,1) res.ai = std::max(res.ai,ai+t.ak);
            }
        }
        return res;
    }
}val[MAXN],sm[MAXN<<2],lst,now;

int fa[MAXN],dfn[MAXN],nfd[MAXN],id[MAXN],dep[MAXN],sz[MAXN],son[MAXN],tp[MAXN];
LL fMAXN,lfMAXN;

inline void dfs1(int v,int faa=0){
    fa[v] = faa;sz[v] = 1;
    for(auto x:G[v]){
        if(x == faa) continue;
        dep[x] = dep[v] + 1;dfs1(x,v);
        sz[v] += sz[x];
        if(!son[v] || sz[son[v]] < sz[x]) son[v] = x;
    }
}

inline void dfs2(int v,int t=1,int fa=0){
    static int ts = 0;dfn[v] = ++ts;nfd[t] = ts;
    id[ts] = v;tp[v] = t;
    if(son[v]) dfs2(son[v],t,v);
    for(auto x:G[v]){
        if(x == fa || x == son[v]) continue;
        dfs2(x,x,v);
    }
}

inline void dfs3(int v,int fa=0){
    lfv = a[v];
    for(auto x:G[v]){
        if(x == fa || x == son[v]) continue;
        dfs3(x,v);
        lfv += std::max(fx,fx);
        lfv += fx;
    }
    fv += lfv;
    fv += lfv;
    if(!son[v]) return;
    dfs3(son[v],v);
    fv += std::max(f[son[v]][0],f[son[v]][1]);
    fv += f[son[v]][0];
}

inline void pushup(int x){
    sm[x] = sm[lc]*sm[rc];
}

inline void build(int x,int l,int r){
    if(l == r){
        val[id[l]].a0 = lf[id[l]][0];val[id[l]].a0 = lf[id[l]][0];
        val[id[l]].a1 = lf[id[l]][1];val[id[l]].a1 = MIN;
        sm[x] = val[id[l]];return;
    }
    int mid = (l + r) >> 1;
    build(lc,l,mid);build(rc,mid+1,r);
    pushup(x);
}

inline Matrix query(int x,int l,int r,int L,int R){
    if(l == L && r == R) return sm[x];
    int mid = (l + r) >> 1;
    if(R <= mid) return query(lc,l,mid,L,R);
    if(L > mid) return query(rc,mid+1,r,L,R);
    return query(lc,l,mid,L,mid)*query(rc,mid+1,r,mid+1,R);
}

inline void update(int x,int l,int r,int pos){
    if(l == r && l == pos){
        sm[x] = val[id[pos]];
        return;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(lc,l,mid,pos);
    else update(rc,mid+1,r,pos);
    pushup(x);
}

inline void change(int v,LL w){
    //val[v].a1 += w-a[v];
    val[v].a1 += w;
    a[v] += w;
    while(v){
        int t = tp[v];
        lst = query(1,1,n,dfn[t],nfd[t]);
        update(1,1,n,dfn[v]);
        now = query(1,1,n,dfn[t],nfd[t]);
        v = fa[t];
        val[v].a0 += std::max(now.a0,now.a1) - std::max(lst.a0,lst.a1);
        val[v].a0 = val[v].a0;
        val[v].a1 += now.a0-lst.a0;
    }
}
LL sum = 0,xx;
std::set<int> S[MAXN];
signed main(){
    read(n);read(m);read(xx);DEBUG(xx);
    FOR(i,1,n) read(a[i]),sum += a[i];
    FOR(i,1,n-1){
        int u,v;read(u);read(v);
        G[u].pb(v);G[v].pb(u);S[u].insert(v);S[v].insert(u);
    }dep[1] = 1;
    dfs1(1);dfs2(1);dfs3(1);build(1,1,n);
    FOR(i,1,m){
        LL a,x,b,y;read(a);read(x);read(b);read(y);//scanf("%lld%lld%lld%lld",&a,&x,&b,&y);
        if(x == 0 && y == 0 && S[a].count(b)){
            puts("-1");continue;
        }
        x = x ? INT_MAX : INT_MIN;y = y ? INT_MAX : INT_MIN;
        x = -x;y = -y;
        change(a,x);change(b,y);
        Matrix res = query(1,1,n,dfn[1],nfd[1]);
        LL ans = std::max(res.a0,res.a1);
        printf("%lldn",sum-ans+std::max(x,0ll)+std::max(y,0ll));
        change(a,-x);change(b,-y);
    }
    return 0;
}

Last modification:April 7th, 2020 at 10:14 pm
如果觉得我的文章对你有用,请随意赞赏