題意

背包問題。給定一個正整數 KK,和 NN 個二元組 (Ci,Vi)(C_i, V_i),表示每個物品的體積及價值。對於所有的 1iK1 \le i \le K,求出當背包容積為 KK 時,最大的物品價值和。

解析

容易注意到的一點是,CC 的值域非常小。考慮定義 fi(j)f_i(j) 表示考慮體積不超過 ii 的物品,背包體積為 jj 時的答案。令 Si(j)S_i(j) 表示所有體積為 ii 的物品的前 jj 大的和,體積相同的物品我們只需要考慮價值最大的幾個就行了。容易寫出轉移方程:

fi(j)=max0kjkfi1(jik)+wi(k)f_i(j) = \max\limits_{0 \le k \le \frac{j}{k}} f_{i-1}(j-ik)+w_{i}(k)

從這個轉移可以看出來,所有的 fi(j)f_i(j) 只和所有 jk(modi)j \equiv k \pmod ifi(k)f_i(k) 有關。按照餘數分類,為了方便,令 gi,r(j)=fi(ij+r)g_{i, r}(j) = f_i(ij + r),上面的轉移方程可以寫作:

gi,r(j)=max0k<jgi1,r(k)+wi(jk)=min0k<jgi1,r(k)wi(jk)\begin{aligned} g_{i, r}(j) &= \max\limits_{0 \le k < j} g_{i-1, r}(k) + w_{i}(j-k) \\ &= -\min\limits_{0 \le k < j} -g_{i-1, r}(k) - w_{i}(j-k) \end{aligned}

對於 abcda \le b \le c \le d,由於 ww 單調遞增且增量不斷減小,可以發現:

w(da)w(ca)w(db)w(cb)w(ca)w(db)w(cb)w(da)\begin{aligned} w(d-a)-w(c-a) &\le w(d-b)-w(c-b) \\ -w(c-a)-w(d-b) &\le -w(c-b)-w(d-a) \end{aligned}

ww 滿足四邊形不等式,gg 有決策單調性,可以用那種常見的分治最佳化。時間複雜度 O(NlogN+300KlogK)O(N \log N + 300 K \log K)

實現

const int MAXC = 300;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, k;
	std::cin >> n >> k;

	std::vector<std::vector<int>> a(MAXC + 1);
	for (int i = 0; i < n; i++) {
		int c, v;
		std::cin >> c >> v;
		a[c].emplace_back(v);
	}

	std::vector<long long> f(k + 1);
	for (int i = 1; i <= MAXC; i++) {
		std::sort(a[i].begin(), a[i].end(), std::greater<>());
		std::vector<long long> w(a[i].size() + 1);
		for (size_t j = 0; j < a[i].size(); j++) {
			w[j + 1] = w[j] + a[i][j];
		}

		int ws = w.size() - 1;
		auto g = f;
		std::fill(f.begin(), f.end(), 0);
		auto solve = [&w, ws, &g, &f, c = i](auto &&self, int q, int l, int r, int ll, int rr) -> void
		{
			int mid = l + (r - l) / 2;
			int p = ll;
			for (int i = ll; i < rr && i <= mid; i++) {
				if (g[p * c + q] + w[std::min(mid - p, ws)] <
				    g[i * c + q] + w[std::min(mid - i, ws)]) {
					p = i;
				}
			}
			f[mid * c + q] = g[p * c + q] + w[std::min(mid - p, ws)];
			if (l < mid) self(self, q, l, mid, ll, p + 1);
			if (mid + 1 < r) self(self, q, mid + 1, r, p, rr);
		};
		for (int j = 0; j < i; j++) {
			if (k - j < 0) continue;
			solve(solve, j, 0, (k - j) / i + 1, 0, (k - j) / i + 1); 
		}
	}

	for (int i = 1; i <= k; i++) {
		std::cout << f[i] << " ";
	}
	std::cout << std::endl;
}