【NOI2019】序列(模拟费用流)(可删堆)

传送门


题解:

第一次凭本事做出 NOI 的 T3 。

首先有一个非常显然的费用流建模如下,边用二元组 ( c a p , c o s t ) (cap,cost) (cap,cost) 表示:

S S S S ′ S' S ( K , 0 ) (K,0) (K,0) S ‘ ’ S‘’ S 分别向 i i i 点连 ( 1 , a i ) (1,a_i) (1,ai) i i i 点向 i ′ i' i 点连 ( 1 , 0 ) (1,0) (1,0) i ′ i' i 点向 T T T ( 1 , b i ) (1,b_i) (1,bi)

这样跑出来全部都是选 a i a_i ai 就必须选 b i b_i bi 的情况,转化题意发现,我们允许最多 K − L K-L KL a i a_i ai b j b_j bj 不匹配。

所以建立中间点 P , Q P,Q P,Q,所有 i i i 点向 P P P 点连 ( 1 , 0 ) (1,0) (1,0) P , Q P,Q P,Q ( K − L , 0 ) (K-L,0) (KL,0),然后 Q Q Q 向所有 i ′ i' i ( 1 , 0 ) (1,0) (1,0)

建图到这里才真正结束,容易发现这样的最大费用最大流就是我们要的答案。

这张图的形式看上去非常简单,考虑模拟费用流。

考虑EK的执行过程,我们每次找到当前可行的最大权值增广,然后维护接下来的选择即可。

凭感觉我们知道关键在于怎么处理 P Q PQ PQ 边,显然能不走 P Q PQ PQ 边的都尽量不要走。

于是很容易得到这样的贪心:

  1. P Q PQ PQ 可用时,选出未选 a a a 的最大值和未选 b b b 的最大值。
  2. P Q PQ PQ 不可用时,我们的选择必须保证形成匹配,有如下三种选择方式:
    • 选择 a i + b i a_i+b_i ai+bi 最大的 i i i
    • 选择 最大的 b i b_i bi ,满足 a i a_i ai 已经被选择 和 最大的未选的 a j a_j aj
    • 选择 最大的 a i a_i ai ,满足 b i b_i bi 已经被选择 和 最大的未选的 b j b_j bj

每次操作完之后,尽可能在 P Q PQ PQ 边上退流即可。

容易发现维护五个可删堆即可,其实由于删除的条件都比较苛刻,所以可删堆可以不用显式维护。我写的是显式维护可删堆。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}template<typename T>T get_integer(){
		char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
		while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
	}inline int gi(){return get_integer<int>();}
}using namespace IO;

using std::cerr;
using std::cout;

cs int N=2e5+7;

cs int INF=2e9; 

struct nd{int vl,id;};
bool operator<(cs nd &a,cs nd &b){
	return a.vl<b.vl||(a.vl==b.vl&&a.id<b.id);
}
bool operator==(cs nd &a,cs nd &b){
	return a.vl==b.vl&&a.id==b.id;
}

#define sm(i) {a[i]+b[i],i}
#define A(i) {a[i],i}
#define B(i) {b[i],i}

typedef std::priority_queue<nd> heap;

struct Heap{
	heap nw,dl;
	void adj(){
		while(!dl.empty()&&dl.top()==nw.top())
			nw.pop(),dl.pop();
	}
	void push(cs nd &a){nw.push(a);}
	void del(cs nd &a){dl.push(a);}
	void pop(){adj();nw.pop();}
	nd top(){adj();return nw.top();}
	bool empty(){return adj(),nw.empty();}
};

int n,K,L,ava;
int a[N],b[N];
bool ai[N],bi[N];

void Main(){
	n=gi(),K=gi(),L=gi();ava=K-L;
	for(int re i=1;i<=n;++i)a[i]=gi();
	for(int re i=1;i<=n;++i)b[i]=gi();
	memset(ai+1,0,sizeof(bool)*n);
	memset(bi+1,0,sizeof(bool)*n);
	Heap S,avA,avB,sgA,sgB;
	for(int re i=1;i<=n;++i){
		S.push(sm(i));
		avA.push(A(i));
		avB.push(B(i));
	}ll ans=0;
	while(K--){
		if(ava){
			nd ta=avA.top();
			nd tb=avB.top();
			avA.pop();avB.pop();
			ans+=ta.vl+tb.vl;
			ai[ta.id]=true;
			bi[tb.id]=true;
			if(ta.id!=tb.id){
				--ava;
				if(bi[ta.id]){
					++ava;sgA.del(A(ta.id));
				}else {
					sgB.push(B(ta.id));
					S.del(sm(ta.id));
				}
				if(ai[tb.id]){
					++ava;sgB.del(B(tb.id));
				}else {
					sgA.push(A(tb.id));
					S.del(sm(tb.id));
				}
			}else S.del(sm(ta.id));
			continue;
		}int vs=-INF,va=-INF,vb=-INF;
		if(!S.empty())vs=S.top().vl;
		if(!sgB.empty())va=avA.top().vl+sgB.top().vl;
		if(!sgA.empty())vb=avB.top().vl+sgA.top().vl;
		int mx=std::max({vs,va,vb});ans+=mx;
		if(mx==vs){
			int id=S.top().id;S.pop();
			ai[id]=bi[id]=true;
			avA.del(A(id));avB.del(B(id));
		}else if(mx==va){
			int id=sgB.top().id;sgB.pop();
			bi[id]=true;avB.del(B(id));
			id=avA.top().id;avA.pop();ai[id]=true;
			if(bi[id]){
				++ava;sgA.del(A(id));
			}else {
				sgB.push(B(id));
				S.del(sm(id));
			}
		}else {
			int id=sgA.top().id;sgA.pop();
			ai[id]=true;avA.del(A(id));
			id=avB.top().id;avB.pop();bi[id]=true;
			if(ai[id]){
				++ava;sgB.del(B(id));
			}else {
				sgA.push(A(id));
				S.del(sm(id));
			}
		}
	}cout<<ans<<"\n";
}

inline void file(){
#ifdef zxyoi
	freopen("sequence.in","r",stdin);
#endif
}signed main(){file();int T=gi();while(T--)Main();return 0;} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值