ICPC澳门20I——树链剖分优化空间复杂度,好题
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ICPC澳门20I——树链剖分优化空间复杂度,好题相关的知识,希望对你有一定的参考价值。
注意,空间限制是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
,并且u
是v
的父亲。 - DEL操作,
u = fa[u]
。 - 维护
tim
,tim[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 (树链剖分-线性变换线段树)