题解:CF2061I Kevin and Nivek

题意:给出 \(n\) 场比赛,有两种比赛:

  1. A 花费 \(a_i\) 的代价可以获胜,否则 B 胜。

  2. 比较前面 \(i-1\) 场比赛中谁胜的更多,则该场比赛谁胜,特别的,如果两者赢的场数一样,则 A 胜。

求对于 \(k=0,1\cdots n\),A 要赢下 \(k\) 场比赛的最小代价。

做法:

直接做可以轻松做到 \(n^2\),定义 \(dp_{i,j}\) 为前 \(i\) 场赢了 \(j\) 次,直接 dp 就好。

接下来就相当困难了,这个第二种比赛非常难以转移。

先思考如果没有第二种比赛,那么贪心取就可以了。

我们这么考虑,进行分治,我们考虑将 \(l\) 处的转移到 \(r\) 处。这样转移看似没有什么意义,但是我们可以发现一件事情,如果 $|j-(l-j)| > r - l $,那么要不然全赢要不然全输,这些状态就可以直接只考虑第一种比赛贪心取,这个可以当成是一个凸包和一个非凸包做 \((min,+)\) 卷积。那么对于剩余的状态,我们就递归让他去 \([l,mid]\)\([mid,r]\) 转移。这样每次下传的状态数只有 \(O(r-l)\) 种,每层中的复杂度是 \(O(n\log n)\),所以总的复杂度是 \(O(n\log ^2 n)\)

实现上,我采用的是 unordered_map 去实现记录可能的转移点,用 map 略微卡常(可能是我写法比较大常吧),可能在做卷积的时候用二分队列会快一点。

说的可能比较简单,不太理解可以结合代码食用。

代码:

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
const int maxn = 3e5 + 5;
int n, a[maxn], t;
long long w[maxn], tot;
unordered_map<int, long long> f[maxn];
void renew(int pos, int p, long long v) {
	if(!f[pos].count(p))
		f[pos][p] = v;
	else
		f[pos][p] = min(f[pos][p], v);
}
vector<int> pos;
void solve(int lx, int rx, int l, int r, int p, vector<pair<int, long long> > &v) {
	if(l > r)
		return ;
//	cout << lx << " " << rx << endl;
	int mid = l + r >> 1, px = lx;
	long long ans = 9e18;
	for (int i = lx; i <= rx; i++) {
		int id = pos[mid] - t - v[i].first;
		if(id >= 0 && id <= tot) {
			long long val = w[id] + v[i].second;
			if(val < ans)
				ans = val, px = i;
		}
	}
//	if(pos[mid] == 5)
//		cout << "Asdfsadf" << lx << " " << rx << " " << px << " " << ans << endl;
	renew(p, pos[mid], ans);
	solve(lx, px, l, mid - 1, p, v);
	solve(px, rx, mid + 1, r, p, v);
}
void cal(int p, int lim, vector<pair<int, long long> > &v) {
	int cur = -1;
	sort(v.begin(), v.end());
	pos.clear();
//	cout << lim << "asdfadf" << endl;
	for (int i = 0; i < v.size(); i++) {
		int l = v[i].first + lim, r = l + tot;
		for (int j = max(l, cur + 1); j <= r; j++) {
			if(j > cur)
				pos.push_back(j);
		}
		cur = r;
	}
//	cout << endl << v.size() << endl;
	if(pos.size() && v.size())
		solve(0, v.size() - 1, 0, pos.size() - 1, p, v);
}
void solve(int l, int r) {
//	cout << l << "asdf" << r << endl;
//	for (map<int, int>::iterator it = f[l].begin(); it != f[l].end(); it++)
//		cout << it -> first << " " << it -> second << endl;
	if(l + 1 == r) {
		if(a[r] == -1) {
			for (unordered_map<int, long long>::iterator it = f[l].begin(); it != f[l].end(); it++) {
				renew(l + 1, it -> first, it -> second);
				if(l - it -> first <= it -> first)
					renew(l + 1, it -> first + 1, it -> second);
			}
		}
		else {
			for (unordered_map<int, long long>::iterator it = f[l].begin(); it != f[l].end(); it++) {
				renew(l + 1, it -> first, it -> second);
				renew(l + 1, it -> first + 1, it -> second + a[r]);
			}
		}
		return ;
	}
	t = tot = 0;
	for (int i = l + 1; i <= r; i++) {
		if(a[i] == -1)
			t++;
		else
			w[++tot] = a[i];
	}
	sort(w + 1, w + tot + 1);
	for (int i = 1; i <= tot; i++)
		w[i] += w[i - 1];
	vector<pair<int, long long> > gw, gl, gm;
	for (unordered_map<int, long long>::iterator it = f[l].begin(); it != f[l].end(); it++) {
		if(it -> first >= r / 2 + 1)
			gw.push_back(*it);
		else if(it -> first <= (2 * l - r - 1) / 2)
			gl.push_back(*it);
		else
			gm.push_back(*it); 
	}
	int mid = l + r >> 1;
	cal(r, t, gw);
	t = 0, cal(r, 0, gl);
	f[l].clear();
	for (int i = 0; i < gm.size(); i++)
		f[l].insert(gm[i]);
	solve(l, mid), solve(mid, r);
}
long long ans[maxn];
void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= n + 1; i++)
		f[i].clear();
	f[0][0] = 0;
	solve(0, n);
	for (int i = 0; i <= n; i++)
		ans[i] = f[n][i];
	for (int i = n; i >= 1; i--)
		ans[i - 1] = min(ans[i - 1], ans[i]);
	for (int i = 0; i <= n; i++)
		cout << ans[i] << " ";
	cout << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	int T; cin >> T;
	while(T--)
		solve(); 
	return 0;
}
/*
1
5
100 -1 -1 -1 1
*/
posted @ 2025-07-23 21:17  LUlululu1616  阅读(12)  评论(0)    收藏  举报