Luogu P5470 [NOI2019]序列

神仙的模拟费用流。再次感谢陈指导的倾情指导

首先我们要想到费用流的做法,这里先直接贴陈指导博客的图了:

很容易发现我们加入的\(p\to p'\)的边容量为\(k-l\),那么显然会有至少\(l\)条边经过了\(i\to i'\)的路径

最大费用最大流极为答案

考虑模拟费用流,模拟费用流的本质其实就是对费用流的模拟,因此对于接下来的操作我们都会给出它在上图中对应的流法

我们考虑经过\(p\to p'\)的一定是最优的边,因此我们需要先贪心地把这条边流满

具体的,我们可以对每个序列各开一个堆,每次选出还未被选择过的\(a\)中的最大元素\(a_x\)和还未被选择过的\(b\)中的最大元素\(b_y\),将总剩余流量\(k\)\(p\to p'\)的剩余流量减\(1\)

\(S\to S'\to x\to p\to p'\to y'\to T\)

但是如果\(b_x\)或者\(a_y\)已经被选择过了,那么我们可以进行修改,让它们不流\(p\to p'\)这条任选边,而是改走一般的边,将\(p\to p'\)的剩余流量加\(1\)

\(S\to S'\to x\to x'\to T\)\(S\to S'\to y\to y'\to T\)

\(p\to p'\)被流满时,接下来我们有三种选择:

  • 选择一对都未被选过的\(a_x,b_x\),并选择它们。(\(S\to S'\to x\to x'\to T\)
  • 选择一个最大的\(a_x\),以及一个最大的\(a_y\)已经被选过的\(b_y\)。考虑把原来与\(a_y\)通过\(p\to p'\)边链接的\(b\)\(a_x\)配对,而\(a_y\)\(b_y\)自己走\(y\to y'\)边。但是需要注意若\(b_x\)已经被选过的话,我们需要将\(p\to p'\)的剩余流量加\(1\)并重复之前的操作。(\(S\to S'\to x\to p\to v\to v'\to T\)
  • 选择一个最大的\(b_y\),以及一个最大的\(b_x\)已经被选过的\(a_x\)。做法和原理同上

因此我们现在只需要模拟上面的操作即可,具体的,我们开\(5\)个堆

分别表示两个序列中所有未选的元素,以及两个序列中未选但对应元素已被选的元素,以及下标相同且都未被选的元素对

总复杂度\(O(n\log n)\)

#include<cstdio>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e9;
struct element
{
	int v,p;
	inline element(CI V=0,CI P=0) { v=V; p=P; }
	friend inline bool operator < (const element& A,const element& B)
	{
		return A.v<B.v;
	}
}x,y; priority_queue <element> A,B,AA,BB,C;
int t,n,k,l,ta[N],tb[N]; bool p[N],q[N]; long long ans;
inline bool PA(CI x)
{
	p[x]=1; if (q[x]) return 1; else return BB.push(element(tb[x],x)),0;
}
inline bool PB(CI x)
{
	q[x]=1; if (p[x]) return 1; else return AA.push(element(ta[x],x)),0;
}
inline int gettop(priority_queue <element>& hp)
{
	if (hp.empty()) return -INF; else return hp.top().v;
}
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d%d",&n,&k,&l),i=1;i<=n;++i) scanf("%d",&ta[i]),p[i]=0;
		for (i=1;i<=n;++i) scanf("%d",&tb[i]),q[i]=0; while (!C.empty()) C.pop();
		while (!A.empty()) A.pop(); while (!AA.empty()) AA.pop();
		while (!B.empty()) B.pop(); while (!BB.empty()) BB.pop();
		ans=0; int w=k-l; for (i=1;i<=n;++i) A.push(element(ta[i],i)),B.push(element(tb[i],i));
		while (k&&w&&!A.empty())
		{
			x=A.top(); A.pop(); y=B.top(); B.pop(); ans+=ta[x.p]+tb[y.p];
			if (PA(x.p)) ++w; if (PB(y.p)) ++w; --k; --w;
		}
		for (i=1;i<=n;++i) if (!p[i]&&!q[i]) C.push(element(ta[i]+tb[i],i));
		while (k)
		{
			while (!C.empty()&&(p[C.top().p]||q[C.top().p])) C.pop();
			while (!A.empty()&&p[A.top().p]) A.pop(); while (!AA.empty()&&p[AA.top().p]) AA.pop();
			while (!B.empty()&&q[B.top().p]) B.pop(); while (!BB.empty()&&q[BB.top().p]) BB.pop();
			--k; int a=gettop(C),b=gettop(A)+gettop(BB),c=gettop(AA)+gettop(B);
			if (a>=b&&a>=c) x=C.top(),C.pop(),ans+=a,p[x.p]=q[x.p]=1; else
			if (b>=a&&b>=c) x=A.top(),A.pop(),y=BB.top(),BB.pop(),ans+=b,q[y.p]=1,PA(x.p)&&(++w);
			else x=AA.top(),AA.pop(),y=B.top(),B.pop(),ans+=c,p[x.p]=1,PB(y.p)&&(++w);
			while (k&&w)
			{
				while (!A.empty()&&p[A.top().p]) A.pop(); while (!B.empty()&&q[B.top().p]) B.pop();
				x=A.top(); A.pop(); y=B.top(); B.pop(); ans+=ta[x.p]+tb[y.p];
				if (PA(x.p)) ++w; if (PB(y.p)) ++w; --k; --w;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2020-08-22 14:21  空気力学の詩  阅读(109)  评论(0编辑  收藏  举报