[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();
}
总结
这一类权值为减法的问题, 考虑把减数和被减数分开考虑

浙公网安备 33010602011771号