【题解】 「NOI2019」序列 模拟费用流 LOJ3158

Legend

给定两个长 \(n\) 的序列 \(A,B\),你需要从两个序列中各自选 \(K\) 个数出来,满足下标相同的对数至少 \(L\) 个。

求选出来的数的最大的和。

\(1 \le L \le K \le n \le 2\times 10^6\)。

Editorial

考虑费用流,把“下标相同的对数至少 \(L\) 个”转化为“下标不同的对数最多 \(K-L\)”个。

【题解】 「NOI2019」序列 模拟费用流 LOJ3158

其中底部蓝边流量为 \(K-L\),表示下标任意选的对。

中间绿边流量为 \(1\),表示选下标相同的对。

\(S,T\) 连出/连入的边流量为 \(1\),费用为这个数字的大小。

然后跑最大费用最大流就行了。

然而这么简单的题怎么可能成为 NOID1T3?显然,我们 TLE 了。(不过在考场上有这部分分已经很好了)


模拟费用流

什么是模拟费用流?字面意思。当费用流的图有特殊性时,我们可以用贪心的策略进行调整流量(流新流量和退流两个操作),而不用跑最短(长)路。

在这个题中,我们也可以考虑贪心+调整的模式。

首先,对于 \(K-L\) 个可以*配对的下标,我们一定先选最优。

然后对于剩余的,我们考虑调整流量。

以下图为例:

【题解】 「NOI2019」序列 模拟费用流 LOJ3158

红色表示初始流量,蓝色表示调整后的流量。

我们把配对 \(AB'\) 调整为了 \(AB+A'B'\)。

也就是依靠每次删除一个不同下标对\(AB'\),形成一个新不同下标对 \(A'B'\),和一个相同下标对 \(AB\)。不难发现这样依然满足流量限制。

把退流在实际费用流中的情况画出来就是这样的:

【题解】 「NOI2019」序列 模拟费用流 LOJ3158

形式化地来说每次要比较这三者的最优值:

  1. 找到一个已经配对的 \(A\),考虑将它的配对改成与之下标对应的 \(B\)(还未被选),我们还要找一个左侧没选的最大值,把它和原先与 \(A\) 配对的那个数配对。(退流)

  2. 考虑将图对称,我们也可以对 \(B\) 进行一样的改流操作。(退流)

  3. 考虑 \(A,B\) 都未选,也可以直接选。(流下标相同)

在以上三种情况中,每次选最优的流就可以了。

以及时刻注意,如果在配对的时候不小心出现了两个下标相等的被选但没配对,一定要改流成下标相等,这样一定更优。

所以还加上一个 4:

  1. 如果没有流满不同下标的流,直接选两个最大值流。(流下标不同)

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;
}
上一篇:Linux内核编码技巧之兄弟团圆


下一篇:2021-01-19