ICPC澳门20I——树链剖分优化空间复杂度,好题

Posted hans774882968

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ICPC澳门20I——树链剖分优化空间复杂度,好题相关的知识,希望对你有一定的参考价值。

传送门1或者传送门2

注意,空间限制是8MB

这题太综合了,能独立写出来说明你能在澳门站拿金了。另外,树链剖分优化空间复杂度真的是我第一次听说~

首先,它考察了一个博弈论的结论:若干石堆的Nim游戏,后手必胜等价于各石堆数异或和为0。所以原题等价于以下问题:保留一个子集(可空)的石堆,a属性的异或和为0,且b属性的和最大。这显然是一个01背包(大概每一个关注“树链剖分”的人都会吧~),dp表示保留出的最大b属性和,bTot - dp[idx][0]即所求。于是我们有如下代码:

#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;
}

可惜它卡不过去,test case2就MLE了……后来才了解到,可以用离线+轻重链剖分来优化空间。

待填坑:OJ中空间使用的计算方式。

首先,我们使用离线算法,把询问组织成树,即:

  • 维护一个u表示当前指向的点。
  • ADD操作,新建一个点v,并且uv的父亲。
  • DEL操作,u = fa[u]
  • 维护timtim[u]表示点u对应的查询编号的集合。

然后我们考虑这棵树的每个点,不妨设当前在考虑u。我们要求的是:从根到u的路径所表示的石堆的01背包。于是我们需要cur变量表示fa[u]的dp数组编号,即dp[cur]表示fa[u]的dp数组。此时我们有两个选择,把u的dp数组保存在dp[cur]dp[cur+1]。假如没有轻重链剖分的想法,那么cur会达到这棵树的最大深度,于是这个算法和上面的算法没有任何区别。

因此现在我们希望能够复用dp[cur]的空间。我们痛苦地发现,ufa有若干儿子,如果某个儿子u0复用了dp[cur]的空间,那么信息丢失了,后续的儿子都无法求出答案。所以复用dp[cur]空间的儿子只能有一个,并且这个儿子必须最后遍历,不妨把它叫做“红点”。此时cur可能达到的最大值为:根到叶子的路径的非红点最大数目。

什么时候非红点最少呢?采用轻重链剖分的时候。因此红点就是重儿子。下面简要说明为什么路径上轻儿子数目不超过logn:轻儿子v满足:2*siz[v] <= siz[fa[v]] <= n,因此siz至多取logn次轻儿子就到了叶子。实际上,这就是树链剖分一条基本性质:轻儿子数目,就是重链的数目,不超过logn

因此我们的操作如下:如果当前点u是轻儿子,则不复用dp[cur]空间,新开空间来存储。否则覆盖dp[cur]。之后先遍历轻儿子,再遍历重儿子。

代码

#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);//轻儿子不复用。1号点也是轻儿子,但可以复用
    }
    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表示负无穷
    memset(dp[0],-1,sizeof dp[0]);dp[0][0] = 0;
    dfs2(1);
    rep(i,1,m) printf("%d\\n",ans[i]);
    return 0;
}

以上是关于ICPC澳门20I——树链剖分优化空间复杂度,好题的主要内容,如果未能解决你的问题,请参考以下文章

2019 ICPC上海 F.A Simple Problem On A Tree(树链剖分)

2019年ICPC南昌网络赛 J. Distance on the tree 树链剖分+主席树

ACM-ICPC 2018 焦作赛区网络预赛 E. Jiu Yuan Wants to Eat (树链剖分-线性变换线段树)

ACM-ICPC 2018 焦作赛区网络预赛 E Jiu Yuan Wants to Eat (树链剖分+线段树)

BZOJ 3531 SDOI2014 旅行 树链剖分

2019年ICPC南昌网络赛 J. Distance on the tree(树链剖分+主席树 查询路径边权第k大)