1. 程式人生 > 實用技巧 >Luogu P5470 [NOI2019]序列

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;
}