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——

posted @ 2024-10-23 14:36  Sirkey  阅读(4)  评论(0编辑  收藏  举报