AT_tokiomarine2020_d Knapsack Queries on a tree
考试考到的签题。
注意到本题是一颗二叉树,同时 \(n\) 只有 \(2^{18}-1\) 个,也就是 \(17\) 层。
如果我们对每一个询问,把他的祖先储存起来,只有 \(17\) 个。
我们由此可以得到一个相对暴力的状压做法。
我们要优化这个状压做法,可以将采用根号分治。
其实思路非常流畅,当树高小于等于 \(8\) 的时候是合理的,跟深的时候发现可以拆分成两半。一半预处理一半询问时算。
预处理前 \(9\) 层的数,一共 \(1023\) 个。对于每一个我们求,他和他的祖先花费 \(i\) 的最大贡献。
得到转移:
\(f_{i,j} = max(f_{i/2,j-w_i} + v_i, f_{i/2,j},f_{i,j-1})\)
对前 \(9\) 层的求出答案,查询就 \(O(1)\) 了。
后面的层数直接转呀,每一次情况求出花费 \(sum1\) 和贡献 \(sum2\)。
答案是 \(f_{i,L-sum1} + sum\) 取最大值。 \(i\) 是询问的数祖先从大向小第一个小于 \(1023\) 的祖先。
另外空间还是要计算一下。我们 \(f\) 数组开了 \(1024 \times 10^5\) 的大小,所以不可以开 \(longlong\)。
#include<bits/stdc++.h>
#define ll long long
#define debug(x) cout<<#x<<"[:]"<<x,puts("");
#define FOR(i,a,b) for(ll i=(a); i<=(b); ++i)
#define ROF(i,a,b) for(ll i=(a); i>=(b); --i)
#define pb push_back
//
//
//
using namespace std;
inline ll read() {
ll f = 0, t = 0;
char c = getchar();
while (!isdigit(c)) t |= (c == '-'), c = getchar();
while (isdigit(c)) f = (f << 3) + (f << 1) + c - 48, c = getchar();
return t ? -f : f;
}
void write(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int MX = 3e5 + 10;
const int MV = 1e5 + 10;
int W[1030][MV];
int w[MX], v[MX];
int h[30];
signed main() {
ios::sync_with_stdio(0), cout.tie(0);
int n = read();
FOR(i,1,n) v[i] = read(), w[i] = read();
FOR(i,1,1024) {
FOR(k,1,MV - 10) if(k >= w[i]) W[i][k] = max(W[i][k], max(W[i/2][k - w[i]] + v[i], W[i/2][k]));
else W[i][k] = max(W[i][k], W[i/2][k]);
FOR(k,1,MV - 10) W[i][k] = max(W[i][k - 1], W[i][k]);
}
int Q = read();
while(Q--) {
int x = read(), L = read();
if(x <= 1024) cout<<W[x][L]<<"\n";
else {
int ans = 0;
int t = x, top = 0;
while(t>1024) h[++top] = t, t/=2;
FOR(sub,0,(1<<top) - 1) {
int sum1 = 0, sum2 = 0;
FOR(j,0,top - 1) if(sub&(1<<j)) sum1 += v[h[j + 1]], sum2 += w[h[j + 1]];
if(L >= sum2) ans = max(ans, W[t][L - sum2] + sum1);
}
cout<<ans<<'\n';
}
}
return 0;
}
——end——