Loading

[CF 2102] E. 23 Kingdom

思路

首先直接 \(\rm{dp}\) 不行

注意到倘若我们钦定一个点对产生贡献, 那么我们可以任选一个合法的数填上, 然后这个数被废弃
进一步的发现要填的数必定是 \(1 \sim x\) 的, 而且点对的开头应该尽可能在前面选, 结尾尽可能在后面选

合一下
考虑 \(\textrm{pre}_i\) 表示最短有多长的前缀可以包含 \(1 \sim x\)
同理考虑 \(\textrm{suf}_i\) 表示最短有多长的后缀可以包含 \(1 \sim x\)

这两个的计算可以简单扫一遍, 每次贪心的取还没取过的最大数即可, 能取到那么答案加一
直到某个 \(\textrm{pre}_i > \textrm{suf}_i\) 时, 就取不到了, 答案就是后缀下标之和减去前缀下标之和


另外一种做法是
考虑最终的答案是后减前, 也就是后面的下标之和减去前面的下标之和

不难发现数字一定是从小到大匹配的, 我们不妨找到 \(\textrm{pre}_i\) 表示最短有多长的前缀可以包含 \(1 \sim x\)
同理找到 \(\textrm{suf}_i\) 表示最短有多长的后缀可以包含 \(1 \sim x\)

那么这样子搞, 显然可以构成最优解
最终的最优解一定是尽可能早的在这个前缀中放上 \(1 \sim x\) 中的数, 然后再在后缀中放上 \(1 \sim x\) 中的数

参考代码 From RegenFallen
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 2e6 + 5;
typedef long long ll;
 
int a[maxn];
int pre[maxn], suf[maxn];
// pre[i]: 找到i个元素需要前几个数字
// suf[i]: 同理 后缀
set<int> s;
int now = 0;
 
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) suf[i] = pre[i] = 0;
 
    // 每次可以erase一个<=自己的元素
    // 找到upper
    // 如果是begin continue
    // 否则 ans++, s.erase(--pos);
    s.clear();
    for (int i = 1; i <= n; i++) s.insert(i);
    now = 0;
    for (int i = 1; i <= n; i++) {
        auto it = s.upper_bound(a[i]);
        if (it == s.begin()) continue ;
        now++;
        pre[now] = i;
        s.erase(--it);
    }
    
    now = 0;
    for (int i = 1; i <= n; i++) s.insert(i);
    for (int i = n; i >= 1; i--) {
        auto it = s.upper_bound(a[i]);
        if (it == s.begin()) continue ;
        now++;
        suf[now] = i;
        s.erase(--it);
    }
    // 枚举选几个数字
    ll ans = 0;
 
    
    for (int i = 1; i <= n; i++) {
        if (pre[i] >= suf[i]) break;
        ans += suf[i] - pre[i];
    }
    cout << ans << endl;
 
}
 
int main() {
    ios::sync_with_stdio(0);
    int T = 1;
    cin >> T;
    while (T--) solve();
}

总结

这一类权值为减法的问题, 考虑把减数和被减数分开考虑

posted @ 2025-05-12 20:26  Yorg  阅读(81)  评论(0)    收藏  举报