LOJ 3776 - 「BalticOI 2022 Day1」物品(DP)

题面传送门

题意见 NFLSOJ,因为已经讲得很清楚了就不再重复。

首先讲一些考场上一些错误的方向,刚看到这道题你会发现背包必然是逃不掉的,因此你可能会尝试各种方法乱搞背包,包括但不限于定义域值域交换、猜测答案在 \([m^2,sum-m^2]\) 之内必然有解等等错误的尝试,但是都不太行得通。

正确的方向:发现一些性质,从而将背包容量限制在 \(\text{poly}(m)\) 的规模内

我们先考虑一个贪心:先假设所有数全选,求出所有数之和 \(s\),如果 \(s > L\) 就每次删去最大的数直到 \(\le L\) 位置,这个可以直接扫一遍求得,对于 \(s<L\) 的情况也同理。此时所有数之和必然在 \([L-m,L]\) 中,但是这不一定是最优方案,我们还需进一步调整。假设此时 \(i\) 的数量为 \(c_i\),最优解中 \(i\) 的数量为 \(b_i\),那么有结论 \(\sum |b_i - c_i|\le 2m\)。证明大概就,在调整过程中,所有物品重量总和肯定不会走出区间 \([L-m,L+m]\),否则我们可以调整删物品/加物品的顺序,并且我们肯定不会重复经过同一个状态两次,因此我们最多加/删物品 \(2m\) 次。于是直接多重背包即可。背包容量上界是 \(\mathcal O(m^2)\),使用单调队列可以优化到 \(\mathcal O(m^3)\)

总之,是一道不错的发掘性质题。

const int MAXN = 600;
int n; ll L, a[MAXN + 5], b[MAXN + 5], sum, mnV, mxV;
ll dp1[2000000], dp2[2000000];
void ins(ll *dp, int w, int v, ll num, int lim) {
//	printf("ins %d %d %lld %d\n", w, v, num, lim);
    for (int i = (w > 0) ? (-lim) : lim, stp = 0; stp < abs(w); (w > 0) ? (i++) : (i--), stp++) {
        deque<pair<int, ll> > q;
        for (int j = i, k = 0; j <= lim && j >= -lim; j += w, k++) {
            while (!q.empty() && q.back().se < dp[j + lim] - v * k) q.pop_back();
            q.push_back(mp(k, dp[j + lim] - v * k));
            while (!q.empty() && q.front().fi < k - num) q.pop_front();
            if (!q.empty()) dp[j + lim] = v * k + q.front().se;
        }
    }
}
int main() {
	freopen("goods.in", "r", stdin);
	freopen("goods.out", "w", stdout);
	scanf("%d%lld", &n, &L);
	for (int i = -n; i <= n; i++) {
		scanf("%lld", &a[i + n]);
		sum += i * a[i + n], b[i + n] = a[i + n];
		if (i < 0) mnV += i * a[i + n];
		else mxV += i * a[i + n];
	}
	if (L < mnV || L > mxV) return puts("impossible"), 0;
	if (sum == L) {
		ll res = 0;
		for (int i = 0; i <= 2 * n; i++) res += a[i];
		printf("%lld\n", res); return 0;
	}
	int d = (sum > L) ? 1 : -1;
	for (int i = d * n; i != 0; i -= d) {
		ll l = 0, r = a[i + n], p = 0;
		while (l <= r) {
			ll mid = l + r >> 1;
			if ((sum - mid * i >= L) == (d == 1)) p = mid, l = mid + 1;
			else r = mid - 1;
		}
		sum -= p * i; b[i + n] -= p;
	}
	int lim = n * n;
	memset(dp1, 192, sizeof(dp1)); memset(dp2, 192, sizeof(dp2));
	dp1[lim] = dp2[lim] = 0;
	for (int i = -n; i <= n; i++) {
		if (i == 0) continue;
		ll can_del = min(1ll * n, b[i + n]), can_add = min(1ll * n, a[i + n] - b[i + n]);
		ins(dp1, i, 1, can_add, lim); ins(dp2, -i, -1, can_del, lim);
	}
	ll lft = L - sum, res = -1e18;
	for (int i = -lim; i <= lim; i++) if (-lim <= lft - i && lft - i <= lim)
		chkmax(res, dp1[i + lim] + dp2[lft - i + lim]);
	for (int i = -n; i <= n; i++) res += b[i + n];
	if (res < 0) puts("impossible");
	else printf("%lld\n", res);
	return 0;
}
posted @ 2022-06-07 17:32  tzc_wk  阅读(125)  评论(0)    收藏  举报