1. 程式人生 > 其它 >「題解」Codeforces 442C Artem and Array

「題解」Codeforces 442C Artem and Array

人類智慧貪心題

考慮三個相鄰的元素 \(x,y,z\) 滿足 \(y\leq x,z\),則三者先刪除 \(y\)​ 更優。

假設最左側和最右側有兩個不能刪除的 \(0\),發現和原問題等價,這樣就不需要討論邊界情況了。

考慮反證,不失一般性假設先刪除 \(x\).對於任意一種先刪除 \(x\) 的策略,在刪除 \(x\) 的時候先刪除 \(y\) 一定不劣:

假設現在序列是 \(...,v,x,y,z,...\)

  • 如果先刪除 \(x\),對答案貢獻是 \(\min(v,y)\),剩餘序列為 \(...,v,y,z,...\)
  • 如果先刪除 \(y\),對答案貢獻是 \(\min(x,z)\),剩餘序列為 \(...,v,x,z,...\)

注意到 \(\min(v,y)\leq y\leq \min(x,z)\),以及序列中的數越大答案越大,所以後者相比前者不劣。

然後變成雙調的,答案為 \(n-2\) 小的數的和。雖然前面編出來證明了,這裡的構造還是w23c3c3教我的/ll

性質:最大值和次大值一定不會對答案做貢獻,所以它們具體取值對答案無所謂,存在的意義僅為比其他所有的都大。

充分:給出一種構造,如果第三大值在最大值旁邊,則刪去最大值,否則第三大值在次大值旁邊,刪去次大值,遞迴成子問題。

必要:嘗試構造,發現一個數如果對答案貢獻 \(x\) 次,那麼一定有一個比它大的數少貢獻 \((x-1)\) 次,所以每個數僅貢獻一次最優。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#define pb emplace_back
#define mp std::make_pair
#define fi first
#define se second
typedef long long ll;
typedef long double ld;
typedef std::pair<int, int> pii;
typedef std::pair<ll, int> pli;
typedef std::pair<ll, ll> pll;
typedef std::vector<int> vi;
typedef std::vector<pii> vpii;
typedef std::vector<ll> vll;
const ll mod = 998244353;
ll Add(ll x, ll y) { return (x+y>=mod) ? (x+y-mod) : (x+y); }
ll Mul(ll x, ll y) { return x * y % mod; }
ll Mod(ll x) { return x < 0 ? (x + mod) : (x >= mod ? (x-mod) : x); }
ll cadd(ll &x, ll y) { return x = (x+y>=mod) ? (x+y-mod) : (x+y); }
template <typename T> T Max(T x, T y) { return x > y ? x : y; }
template <typename T> T Min(T x, T y) { return x < y ? x : y; }
template <typename T>
T &read(T &r) {
	r = 0; bool w = 0; char ch = getchar();
	while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
	while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
	return r = w ? -r : r;
}
const int N = 500010;
int n, a[N], l[N], r[N], vis[N], b[N], mx, bmx;
ll ans;
std::queue<int>q;
bool check(int x) {
	return x && !vis[x] && a[x] <= a[l[x]] && a[x] <= a[r[x]];
}
signed main() {
	read(n);
	for(int i = 1; i <= n; ++i) read(a[i]), l[i] = i-1, r[i] = i+1; r[n] = 0;
	for(int i = 2; i < n; ++i)
		if(check(i))
			q.push(i), vis[i] = 1;
	while(!q.empty()) {
		int x = q.front(); q.pop();
		ans += Min(a[l[x]], a[r[x]]);
		r[l[x]] = r[x]; l[r[x]] = l[x];
		if(check(l[x])) q.push(l[x]), vis[l[x]] = 1;
		if(check(r[x])) q.push(r[x]), vis[r[x]] = 1;
	}
	for(int i = 1; i <= n; ++i)
		if(!vis[i]) {
			ans += a[i];
			if(a[i] >= mx) bmx = mx, mx = a[i];
			else bmx = Max(bmx, a[i]);
		}
	printf("%lld\n", ans-mx-bmx);
	return 0;
}