【题解】 「NOI2019」序列 模拟费用流 LOJ3158
Legend
给定两个长 \(n\) 的序列 \(A,B\),你需要从两个序列中各自选 \(K\) 个数出来,满足下标相同的对数至少 \(L\) 个。
求选出来的数的最大的和。
\(1 \le L \le K \le n \le 2\times 10^6\)。
Editorial
考虑费用流,把“下标相同的对数至少 \(L\) 个”转化为“下标不同的对数最多 \(K-L\)”个。
其中底部蓝边流量为 \(K-L\),表示下标任意选的对。
中间绿边流量为 \(1\),表示选下标相同的对。
\(S,T\) 连出/连入的边流量为 \(1\),费用为这个数字的大小。
然后跑最大费用最大流就行了。
然而这么简单的题怎么可能成为 NOID1T3?显然,我们 TLE 了。(不过在考场上有这部分分已经很好了)
模拟费用流
什么是模拟费用流?字面意思。当费用流的图有特殊性时,我们可以用贪心的策略进行调整流量(流新流量和退流两个操作),而不用跑最短(长)路。
在这个题中,我们也可以考虑贪心+调整的模式。
首先,对于 \(K-L\) 个可以自由配对的下标,我们一定先选最优。
然后对于剩余的,我们考虑调整流量。
以下图为例:
红色表示初始流量,蓝色表示调整后的流量。
我们把配对 \(AB'\) 调整为了 \(AB+A'B'\)。
也就是依靠每次删除一个不同下标对\(AB'\),形成一个新不同下标对 \(A'B'\),和一个相同下标对 \(AB\)。不难发现这样依然满足流量限制。
把退流在实际费用流中的情况画出来就是这样的:
形式化地来说每次要比较这三者的最优值:
-
找到一个已经配对的 \(A\),考虑将它的配对改成与之下标对应的 \(B\)(还未被选),我们还要找一个左侧没选的最大值,把它和原先与 \(A\) 配对的那个数配对。(退流)
-
考虑将图对称,我们也可以对 \(B\) 进行一样的改流操作。(退流)
-
考虑 \(A,B\) 都未选,也可以直接选。(流下标相同)
在以上三种情况中,每次选最优的流就可以了。
以及时刻注意,如果在配对的时候不小心出现了两个下标相等的被选但没配对,一定要改流成下标相等,这样一定更优。
所以还加上一个 4:
- 如果没有流满不同下标的流,直接选两个最大值流。(流下标不同)
Code
其实吧,感觉挺像带悔贪心的。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
using namespace std;
const int MX = 2e5 + 23;
const LL MOD = 998244353;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
int n ,K ,L ,a[MX] ,b[MX] ,id[MX] ,cho[MX];
bool cmpa(int x ,int y){return a[x] > a[y];}
bool cmpb(int x ,int y){return b[x] > b[y];}
struct nodeA{bool operator()(int &x ,int &y)const{return a[x] < a[y];}};
struct nodeB{bool operator()(int &x ,int &y)const{return b[x] < b[y];}};
struct nodeAB{bool operator()(int &x ,int &y)const{return a[x] + b[x] < a[y] + b[y];}};
priority_queue<int ,vector<int> ,nodeA> Ha ,Fa; // Ha 表示没选的,Fa 表示 a 选了,但对应位置下标的 b 没选
priority_queue<int ,vector<int> ,nodeB> Hb ,Fb; // Hb 表示没选的,Fb 表示 b 选了,但对应位置下标的 a 没选
priority_queue<int ,vector<int> ,nodeAB> Hab; // 表示 ab 都没被选
template<typename T ,typename B>
void clear(priority_queue<T ,vector<int> ,B> &X){
while(!X.empty()) X.pop();
}
void init(int N){
clear(Ha) ,clear(Fa) ,clear(Hb) ,clear(Fb) ,clear(Hab);
for(int i = 1 ; i <= N ; ++i){
cho[i] = 0;
}
}
void solve(){
n = read() ,K = read() ,L = read();
init(n);
for(int i = 1 ; i <= n ; ++i) a[i] = read();
for(int i = 1 ; i <= n ; ++i) b[i] = read();
LL ans = 0;
for(int i = 1 ; i <= n ; ++i) id[i] = i;
std::sort(id + 1 ,id + 1 + n ,cmpa);
for(int i = 1 ; i <= K - L ; ++i) cho[id[i]] |= 1 ,ans += a[id[i]];
std::sort(id + 1 ,id + 1 + n ,cmpb);
for(int i = 1 ; i <= K - L ; ++i) cho[id[i]] |= 2 ,ans += b[id[i]];
int CD = 0;
for(int i = 1 ; i <= n ; ++i){
if(!cho[i]){
Ha.push(i);
Hb.push(i);
Hab.push(i);
}
else if(cho[i] == 1) Hb.push(i) ,Fb.push(i);
else if(cho[i] == 2) Ha.push(i) ,Fa.push(i);
else ++CD;
}
int i ,j;
while(L--){
while(!Ha.empty() && (cho[Ha.top()] & 1)) Ha.pop();
while(!Hb.empty() && (cho[Hb.top()] & 2)) Hb.pop();
while(!Hab.empty() && (cho[Hab.top()])) Hab.pop();
while(!Fa.empty() && (cho[Fa.top()] ^ 2)) Fa.pop();
while(!Fb.empty() && (cho[Fb.top()] ^ 1)) Fb.pop();
if(CD){ // 直接选一对最大值
--CD;
i = Ha.top() ,j = Hb.top();
ans += a[i] + b[j];
cho[i] |= 1 ,cho[j] |= 2;
if(cho[i] ^ 3) Fb.push(i);
if(cho[j] ^ 3) Fa.push(j);
if(i == j) ++CD;
else{
if(cho[i] == 3) ++CD;
if(cho[j] == 3) ++CD;
}
continue;
}
int v1 = 0 ,v2 = 0 ,v3 = 0 ,c1 = 0 ,c2 = 0 ,vmx = 0;
if(!Fb.empty()){
// 去除 b 同下标的 a,换上最大的 a
i = Ha.top() ,j = Fb.top();
v1 = a[i] + b[j];
c1 = cho[i] != 2 ? 1 : 0;
}
if(!Fa.empty()){
// 去除 a 同下标的 b,换上最大的 b
i = Fa.top() ,j = Hb.top();
v2 = a[i] + b[j];
c2 = cho[j] != 3 ? 1 : 0;
}
if(!Hab.empty()){
i = Hab.top();
v3 = a[i] + b[i];
}
vmx = max(max(v1 ,v2) ,v3);
ans += vmx;
if(v1 == v2 && v1 == vmx){
if(c1 >= c2){
i = Ha.top() ,j = Fb.top();
cho[i] |= 1 ,cho[j] |= 2;
if(cho[i] ^ 3) Fb.push(i);
else ++CD;
}
else{
i = Fa.top() ,j = Hb.top();
cho[i] |= 1 ,cho[j] |= 2;
if(cho[j] ^ 3) Fa.push(j);
else ++CD;
}
continue;
}
if(v1 == vmx){
i = Ha.top() ,j = Fb.top();
cho[i] |= 1 ,cho[j] |= 2;
if(cho[i] ^ 3) Fb.push(i);
else ++CD;
continue;
}
if(v2 == vmx){
i = Fa.top() ,j = Hb.top();
cho[i] |= 1 ,cho[j] |= 2;
if(cho[j] ^ 3) Fa.push(j);
else ++CD;
continue;
}
if(v3 == vmx){
i = Hab.top();
Hab.pop();
cho[i] = 3;
continue;
}
}
printf("%lld\n" ,ans);
}
int main(){
int T = read();
for(int i = 1 ; i <= T ; ++i){
solve();
}
return 0;
}