zoukankan      html  css  js  c++  java
  • Luogu P5470 [NOI2019]序列

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

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

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

    最大费用最大流极为答案

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

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

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

    (S o S' o x o p o p' o y' o T)

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

    (S o S' o x o x' o T)(S o S' o y o y' o T)

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

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

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

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

    总复杂度(O(nlog 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
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    大整数乘除法
    java大神进阶之路
    自定义Swap
    指针基础详解
    已知前序中序求后序(转载)
    杭电1003_Max Sum
    回溯法求解迷宫问题
    linux下安装QQ等EXE文件
    java工程中使用freemarker例子
    maven自动部署到tomcat的问题
  • 原文地址:https://www.cnblogs.com/cjjsb/p/13545439.html
Copyright © 2011-2022 走看看