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


    Legend

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

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

    (1 le L le K le n le 2 imes 10^6)

    Editorial

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

    序列

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

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

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

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

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


    模拟费用流

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

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

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

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

    以下图为例:

    序列2

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

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

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

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

    序列3

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

    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
    " ,ans);
    }
    
    int main(){
    	int T = read();
    	for(int i = 1 ; i <= T ; ++i){
    		solve();
    	}
    	return 0;
    }
    
  • 相关阅读:
    python 学习
    快速排序
    U3D AStar 案例
    U3D AStar 算法原理
    .net core 实时通讯
    .net 算法复杂度
    U3D MVC 背包案例
    U3D 对象池
    Unity网络基础(NetWork)
    U3D Socket实现简易通讯
  • 原文地址:https://www.cnblogs.com/imakf/p/14320825.html
Copyright © 2020-2023  润新知