题目连接
本文旨在介绍树上背包的优化。
可见例题,例题中
的数据量让
的朴素树上背包T到飞起,咱们须要考虑优化。
我的会将各类优化讲到极限(固然是本蒟蒻的极限)。
根据一番学习,我也认为上下界优化最简单易理解……
上下界优化这位神犇的博客至关不错了:戳我%他
我也口胡两句吧。
普通作法:html
for (j=m+1;j>=1;--j)//枚举背包容量 for (k=1;k<j;++k)//枚举在子树中选择多少 f[u][j]=max(f[u][j],f[u][k]+f[v][j-k]);
那么size优化很是简单好想:node
for (j=min(m+1,size[u]);j>=1;--j)//枚举背包容量 for (k=1;k<j&&k<=size[v];++k)//枚举在子树中选择多少 f[u][j]=max(f[u][j],f[u][k]+f[v][j-k]);
道理也很简单,选完就那么多,确定不能枚举到超过的。
因而能AC这道题,用时
。
但再想一想,咱们选择的
的下界其实也是会被约束的。
由于选到
的总容量的时候,假定前面的所有取完,
都必需要到达一个值才能知足条件。
例子:
咱们枚举时,好比
的状况,
咱们至少要在
中取a个节点才能达到此容量。
所以就能获得上下界优化:web
void dfs(int u) { siz[u]=1; f[u][1]=a[u]; int i,j,k,v; for (i=head[u];i;i=nxt[i]) { v=to[i]; dfs(v); for (j=min(m+1,siz[u]+siz[v]);j>=2;--j)//这里作了小改动,由于1的更新确定没有意义 for (k=max(1,j-siz[u]);k<=siz[v]&&k<j;++k) f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]); siz[u]+=siz[v]; } }
这里对
数组的更新作了特殊处理,能够更方便地获得前面全部子树的节点数总和。因而更进一步,达到了12s的成绩。
那么还能不能更快呢?实际上是能够的。
咱们发现内层循环须要2个判断语句,有什么办法缩成一个?
固然能够开临时变量来存,但咱们甚至能够换一种dp方式!(思路来源于某位神犇,他的代码用了刷表法无师自通地进行了
优化致使过去“指点”的我转为“%%%”状态)数组
刷表法怎么写呢?其实也很简单:app
void dfs(int u) { siz[u]=1; f[u][1]=a[u]; int i,j,k,v; for (i=head[u];i;i=nxt[i]) { v=to[i]; dfs(v); for (j=min(m,siz[u]);j>=1;--j)//在以前子树&&根中选择的节点数,这里要取1是由于确定要取根节点 for (k=1;k<=siz[v]&&j+k<=m+1;++k)//在当前子树取得节点数 f[u][j+k]=max(f[u][j+k],f[u][j]+f[v][k]); siz[u]+=siz[v]; } }
这时候,咱们就能够将内层循环的两个判断语句合为一个了:svg
void dfs(int now) { size[now] = 1; f[now][1] = w[now]; int v; for (int p = head[now]; p; p = lines[p].next) { v = lines[p].to; dfs(v); for (int j = min(size[now], m); j; --j) for (int k = min(size[v], m + 1 - j); k; --k) f[now][j + k] = max(f[now][j + k], f[now][j] + f[v][k]); size[now] += size[v]; } }
省去了一个判断,对常数的优化仍是不可小觑的。函数
对于例题,因为
过大,开二维确定开不下,确定要扁平化为一维。
由于有一个超级源点,所以背包最大容量其实为
,而
间有
个位置。
故有:oop
inline int pos(const int &x,const int &y) { return x * (m+2) + y;//注意此处x可能为0 }
可是事实上,每次都计算这个pos带来了大量的计算。多大量呢?
当初用填表法时,我将这个函数换成了
,总时间从
提高到了
。
显然由于这个
反复计算,消耗了大量的时间。
那么是否还有比宏定义更优的方法呢?我翻了翻最优解,除了题目做者本人在调整数据规模时的弱数据AC外,第一位是一位名为WarlockAkk的神犇,用时仅
!
这到底是何等黑魔法?我点开源码开始膜拜,因而看到:学习
bfo(i,0,n+1){ d[i]=spa+idx; idx+=m+2; }
这是什么意思呢? 是一个 的数组,因而我恍然大悟:优化
能够预处理出一个映射数组,将二维的对映射数组的访问映射到一维的保存数组中。
具体实现方式:
int dp[100001000]; int *f[MAXN]; //f[i][j] points to the dp arr. int k, pointer = 0; f[0] = &dp[0]; //special for (int i = 1; i <= n; ++i) { pointer += m + 2; f[i] = &dp[pointer]; //special }
咱们将这两行代码插入到读入的循环中,就能够获得映射数组
,咱们就能直接用
来访问了!
而且由于
存的索引直接加上
就能获得地址,咱们实际上避免了两个大数的乘法,而使其变成了加法。
举例:
原先访问方式:
进行了一次乘法一次加法
解析一下就是:
return dp + (x * (m+2) + y);
而如今的访问方式:
解析一下就是:
return (f + x) + y;
效率提高至关显著。
同时注意咱们的预处理方式:
int pointer = 0; pointer += m + 2;
写成加法的形式,与乘法形式对比:
pointer = (m + 2) * i;
效率如何很显然了。
那么下标映射后到底有多快呢?
有多快呢?
咱们看结论吧。
填表法 | 填表法 with O(2) | 刷表法 | 刷表法 with O(2) | 下标映射 + 刷表法 with O(2) |
---|---|---|---|---|
能够发现,吸氧对于这种状况提高不明显。
而下标映射 快、极快、巨快!
所以在卡常优化时咱们能够多想一想使用指针等玄学进行优化,每每会有意想不到的提高。
如
等函数直接使用迭代器等……
That’s all.
#pragma GCC target("avx") #pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize("Ofast") #pragma GCC optimize("inline") #pragma GCC optimize("-fgcse") #pragma GCC optimize("-fgcse-lm") #pragma GCC optimize("-fipa-sra") #pragma GCC optimize("-ftree-pre") #pragma GCC optimize("-ftree-vrp") #pragma GCC optimize("-fpeephole2") #pragma GCC optimize("-ffast-math") #pragma GCC optimize("-fsched-spec") #pragma GCC optimize("unroll-loops") #pragma GCC optimize("-falign-jumps") #pragma GCC optimize("-falign-loops") #pragma GCC optimize("-falign-labels") #pragma GCC optimize("-fdevirtualize") #pragma GCC optimize("-fcaller-saves") #pragma GCC optimize("-fcrossjumping") #pragma GCC optimize("-fthread-jumps") #pragma GCC optimize("-funroll-loops") #pragma GCC optimize("-fwhole-program") #pragma GCC optimize("-freorder-blocks") #pragma GCC optimize("-fschedule-insns") #pragma GCC optimize("inline-functions") #pragma GCC optimize("-ftree-tail-merge") #pragma GCC optimize("-fschedule-insns2") #pragma GCC optimize("-fstrict-aliasing") #pragma GCC optimize("-fstrict-overflow") #pragma GCC optimize("-falign-functions") #pragma GCC optimize("-fcse-skip-blocks") #pragma GCC optimize("-fcse-follow-jumps") #pragma GCC optimize("-fsched-interblock") #pragma GCC optimize("-fpartial-inlining") #pragma GCC optimize("no-stack-protector") #pragma GCC optimize("-freorder-functions") #pragma GCC optimize("-findirect-inlining") #pragma GCC optimize("-fhoist-adjacent-loads") #pragma GCC optimize("-frerun-cse-after-loop") #pragma GCC optimize("inline-small-functions") #pragma GCC optimize("-finline-small-functions") #pragma GCC optimize("-ftree-switch-conversion") #pragma GCC optimize("-foptimize-sibling-calls") #pragma GCC optimize("-fexpensive-optimizations") #pragma GCC optimize("-funsafe-loop-optimizations") #pragma GCC optimize("inline-functions-called-once") #pragma GCC optimize("-fdelete-null-pointer-checks") #include <cstdio> using namespace std; const int MAXN = 100100; inline int max(const int &a, const int &b) { return a > b ? a : b; } inline int min(const int &a, const int &b) { return a < b ? a : b; } char buf[100000], *p1 = buf, *p2 = buf; #define nc() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++ template <typename T> inline void read(T &r) { static char c;r = 0; for (c = nc(); c > '9' || c < '0'; c = nc()); for (; c >= '0' && c <= '9'; r = (r << 1) + (r << 3) + (c ^ 48), c = nc()); } struct node { int to, next; node() {} node(const int &_to, const int &_next) : to(_to), next(_next) {} } lines[MAXN]; int head[MAXN]; void add(const int &x, const int &y) { static int tot = 0; lines[++tot] = node(y, head[x]), head[x] = tot; } int n, m; int dp[100001000]; int *f[MAXN]; //f[i][j] points to the dp arr. int size[MAXN], w[MAXN]; void dfs(int now) { int v; size[now] = 1; f[now][1] = w[now]; for (int p = head[now]; p; p = lines[p].next) { v = lines[p].to; dfs(v); for (int i = min(size[now], m); i; --i) for (int j = min(size[v], m + 1 - i); j; --j) f[now][i + j] = max(f[now][i + j], f[now][i] + f[v][j]); size[now] += size[v]; } } int main() { read(n); read(m); int k, pointer = 0; f[0] = &dp[0]; //special for (int i = 1; i <= n; ++i) { pointer += m + 2; f[i] = &dp[pointer]; //special read(k); add(k, i); //we can set the point(0) into a vitual node,which is the root of the tree read(w[i]); } dfs(0); printf("%d", f[0][m + 1]); return 0; }~~~