P4138

[JOISC2014] 挂饰

题目描述

JOI君有N个装在手机上的挂饰,编号为1...N。 JOI君可以将其中的一些装在手机上。

JOI君的挂饰有一些与众不同——其中的一些挂饰附有可以挂其他挂件的挂钩。每个挂件要么直接挂在手机上,要么挂在其他挂件的挂钩上。直接挂在手机上的挂件最多有1个。

此外,每个挂件有一个安装时会获得的喜悦值,用一个整数来表示。如果JOI君很讨厌某个挂饰,那么这个挂饰的喜悦值就是一个负数。

JOI君想要最大化所有挂饰的喜悦值之和。注意不必要将所有的挂钩都挂上挂饰,而且一个都不挂也是可以的。

\(1\leq N\leq 2000\)

\(0\leq Ai\leq N(1\leq i\leq N)\)

\(-10^6\leq Bi\leq 10^6(1\leq i\leq N)\)


大概是用到了一点构造思想的简单 dp?

挂饰可以看成一棵树,针对树,仍然从“菊花图/链/拓扑序”来下手。

菊花图不必多说:假定一开始手机上就有一个挂钩即可。接下来考虑菊花图下多出来菊花,很显然可以将这个小菊花合并到大菊花上面去。由此可以发现:我们不关心树的形态,父子关系,仅仅关心“往下可以接多少点”和“权和”。也许可以看成是“对于要构造信息的简化”?

接一个挂钩,会让可挂的数量加上 \(A_i - 1\),同时权加上 \(B_i\)。如果开始默认挂钩 \(0\) 个最后只需要 \(A_i - 1\) 和大于等于 \(-1\)(因为一开始还有一个挂钩没有计算进去)。这就是简单的 01 背包了。

然而还有一个小细节:\((A_i-1)\) 总和很大!不过这没关系,当 \((A_i - 1)\) 在某个阶段的和达到了 \(N\) 以后再变大其实也没有用了,因为此后无论如何都不会使得 \((A_i-1)\) 和小于 \(-1\)。将这些状态合并即可。相应的,实现中利用刷表法更容易。类似的细节处理出现在飞扬的小鸟中。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2000, V = 4000;
const ll inf = 1e10;
int n;
ll f[N + 10][V + 10], A[N + 10], B[N + 10];
int main() {
	cin >> n;
	for(int i = 1; i <= n; i++)
		cin >> A[i] >> B[i], A[i]--;
	
	for(int i = 0; i <= n; i++)
		for(int j = 0; j <= V; j++)
			f[i][j] = -inf;
	f[0][N] = 0;
	
	ll maxn = -inf;
	for(int i = 0; i < n; i++) {
		for(int j = 0; j <= V; j++)
			f[i + 1][j] = f[i][j];
		for(int j = 0; j <= V; j++) {
			int T = min((int)j + (int)A[i + 1], V);
			if(T < 0) continue;
			f[i + 1][T] = max(f[i + 1][T], f[i][j] + B[i + 1]);
		}
		
		for(int j = 0; j <= V; j++)
			if(j >= N - 1)
				maxn = max(maxn, f[i][j]);
	}
	for(int j = 0; j <= V; j++)
		if(j >= N - 1)
			maxn = max(maxn, f[n][j]);
		
	cout << maxn << endl;
} 
posted @ 2024-11-16 21:28  SIXIANG32  阅读(3)  评论(0编辑  收藏  举报