ICPC Macao 20I - tree chain subdivision optimization, space complexity, good problem

Keywords: C++ Dynamic Programming

Portal 1 perhaps Portal 2

Note that the space limit is 8MB!

This question is too comprehensive. If you can write it independently, it means that you can get gold at Macao station. In addition, the space complexity of tree chain subdivision optimization is really the first time I heard~

Firstly, it examines a conclusion of game theory: in the Nim game of several stone piles, the backhand must win is equivalent to the exclusive or sum of the number of stone piles is 0. Therefore, the original problem is equivalent to the following problem: for a stone pile with a subset (nullable), the XOR sum of attribute a is 0, and the sum of attribute b is the largest. This is obviously a 01 knapsack (probably everyone who pays attention to "tree chain segmentation ~). dp represents the maximum b attribute sum reserved, bTot - dp[idx][0] is what you want. So we have the following code:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 20000 + 5;

int n,m,totVal,a[N],b[N];vector<unordered_map<int,int> > dp;

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int calc(int i,int j){
    if(!i) return !j ? 0 : INT_MIN;
    if(dp[i].count(j)) return dp[i][j];
    int v1 = dp[i-1].count(j^a[i]) ? dp[i-1][j^a[i]] : calc(i-1,j^a[i]),
        v2 = dp[i-1].count(j) ? dp[i-1][j] : calc(i-1,j);
    return dp[i][j] = max(v1 > INT_MIN ? v1 + b[i] : INT_MIN,v2);
}

int main(int argc, char** argv) {
    read(m);n = 0;totVal = 0;
    dp.push_back(unordered_map<int,int>());dp[0][0] = 0;
    while(m--){
        char op[5];scanf("%s",op);
        if(op[0] == 'A'){
            ++n;read(a[n]);read(b[n]);
            totVal += b[n];
            dp.push_back(unordered_map<int,int>());
            calc(n,0);
        }
        else{
            dp.pop_back();
            totVal -= b[n];
            --n;
        }
        printf("%d\n",totVal - dp[n][0]);
    }
    return 0;
}

Unfortunately, it can't get stuck. test case2 is MLE... It was learned later that offline + light chain segmentation can be used to optimize the space.

Pit to be filled: calculation method used for space in OJ.

First, we use an offline algorithm to organize queries into a tree, that is:

  • Maintain a u that represents the point you are currently pointing to.
  • ADD operation, create a new point v, and u is the father of V.
  • DEL operation, u = fa[u].
  • Maintain tim. tim[u] represents the set of query numbers corresponding to point U.

Then let's consider each point of the tree. Let's set the current point under consideration u. What we require is: the 01 backpack of the stone pile represented by the path from the root to u. Therefore, we need the cur variable to represent the dp array number of fa[u], that is, dp[cur] represents the dp array of fa[u]. At this point, we have two choices. Save the dp array of u in dp[cur] or dp[cur+1]. If there is no idea of light chain segmentation, cur will reach the maximum depth of the tree, so there is no difference between this algorithm and the above algorithm.

So now we want to reuse the space of dp[cur]. We painfully find that ufa has several sons. If a son u0 reuses the space of dp[cur], the information is lost, and the subsequent sons are unable to find the answer. Therefore, there can only be one son of reused dp[cur] space, and this son must traverse finally. It might as well be called "red dot". At this time, the maximum cur may reach is the maximum number of non red dots in the path from root to leaf.

When are the non red spots the least? When using light chain subdivision. So the red dot is a heavy son. The following is a brief explanation of why the number of light sons on the path does not exceed logn: light son V meets: 2 * siz [v] < = siz [Fa [v]] < = n, so siz takes logn at most, and the light son arrives at the leaf. In fact, this is a basic property of tree chain dissection: the number of light sons is the number of heavy chains, which does not exceed logn.

Therefore, our operation is as follows: if the current point u is a light son, the dp[cur] space is not reused and a new space is opened for storage. Otherwise, dp[cur] is overwritten. After that, first traverse the light son, and then traverse the heavy son.

code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 20000 + 5,M = 16384;

int n,m,a[N],b[N],btot[N],siz[N],son[N];
vector<int> tim[N],G[N];
int ans[N<<1],cur = 0,dp[23][M+5],tmp[M+5];

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

void dfs1(int u,int ufa = 0){
    siz[u] = 1;btot[u] = b[u] + btot[ufa];int mx = 0;
    for(int v: G[u]){
        dfs1(v,u);
        siz[u] += siz[v];
        if(mx < siz[v]){
            mx = siz[v];
            son[u] = v;
        }
    }
}

void dfs2(int u,bool use = true){
    memcpy(tmp,dp[cur],sizeof tmp);
    re_(i,0,M) if(~dp[cur][i]){
        tmp[i^a[u]] = max(dp[cur][i^a[u]],dp[cur][i]+b[u]);
    }
    for(int t: tim[u]) ans[t] = btot[u]-tmp[0];
    if(use) --cur;
    memcpy(dp[++cur],tmp,sizeof tmp);
    for(int v: G[u]){
        if(v == son[u]) continue;
        dfs2(v,false);//A light son does not reuse. Point 1 is also a light son, but it can be reused
    }
    if(son[u]) dfs2(son[u],true);
    if(!use) --cur;
}

int main(int argc, char** argv) {
    read(m);n = 1;int u = 1;
    rep(cas,1,m){
        char op[5];scanf("%s",op);
        if(op[0] == 'A'){
            ++n;read(a[n]);read(b[n]);
            G[u].push_back(n);tmp[n] = u;
            u = n;
        }
        else{
            u = tmp[u];
        }
        tim[u].push_back(cas);
    }
    dfs1(1);
    //-1 means negative infinity
    memset(dp[0],-1,sizeof dp[0]);dp[0][0] = 0;
    dfs2(1);
    rep(i,1,m) printf("%d\n",ans[i]);
    return 0;
}

Posted by YorkshireSteve on Sun, 07 Nov 2021 17:15:02 -0800