UNV887 [UNR R8] 最酷的排列 学习笔记
UNV887 [UNR R8] 最酷的排列 学习笔记
题意简述
有一个长度为 \(n\) 的序列 \(A\)。你可以执行任意次这个操作:选择某个区间,将其中所有元素从小到大重排。给定一个下标集合 \(S\)。问 \(\sum_{i\in S}a_i\) 的最小值可能为多少。
\(n\le 2\times 10^5\)。
做法解析
我首先考虑了一个元素能被换到哪些地方,但这个不好考虑,因为往前移往后移都有可能。
然后我转而去考虑关键下标上面的数需要怎样操作才能让它变小。有一个显然的观察是,对于单个下标 \(i\) 而言,重排 \([i,n]\) 可以使其最小化,因为如果重排包括了 \(i\) 之前的一个元素,\(i\) 此时就只能拿到第二小的了,而这样的第二小的元素一定不小于之前最小的元素(之前最小的元素现在最多是第二小的)。
这启示我们,按照重排后缀来考虑问题。实际上这题的正解就是这么简单:维护一个可重集合,从后往前一个一个加元素,碰到关键下标就把当前集合内的最小元素取出来加到答案里面。
没错就这完了。这显然是个答案下界,但为什么它一定可以被构造出来呢?一种构造方式是把所有关键下标和最终选择的元素都从左到右排序,我们分别记为 \(s_1,s_2,\dots ,s_m\) 和 \(a_1,a_2\dots a_m\)。显然有 \(s_i\le a_i\)。然后我们从 \(i=1\) 开始,依次重排 \([s_i,a_i]\) 即可。正确性在于这么做不可能让答案变得更大,而它又是答案下界,既到上界又到下界的东西就是答案了。

代码实现
最简单的一集。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=2e5+5;
int N,A[MaxN],S[MaxN];char ch;
multiset<int> mset;lolo ans;
void mian(){
readi(N);mset.clear(),ans=0;
for(int i=1;i<=N;i++)readi(A[i]);
for(int i=1;i<=N;i++)scanf(" %c",&ch),S[i]=ch-'0';
for(int i=N;i>=1;i--){
mset.insert(A[i]);
if(S[i])ans+=*mset.begin(),mset.erase(mset.begin());
}
writil(ans);
}
int Tcn;
int main(){
readi(Tcn);
while(Tcn--)mian();
return 0;
}
浙公网安备 33010602011771号