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;
}
辣鸡老年选手AFO在即