LOJ#3158. 「NOI2019」序列(贪心模拟费用流)

传送门
神题,蒟蒻博主只能写 O ( n 4 ) O(n^4) O(n4)暴力 d p dp dp(才不告诉你卡常之后貌似能过 40 p t s 40pts 40pts呢)
40 p t s 40pts 40pts代码:

#include
#define ri register int
using namespace std;
typedef long long ll;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=2e5+5;
int n,a[N],b[N],L,K;
namespace subtask1{
	int id[N];
	inline void Main(){
		ll ans=0;
		for(ri i=1;i<=n;++i)id[i]=i;
		priority_queue<int>q1,q2;
		for(ri i=1;i<=20000;++i){
			random_shuffle(id+1,id+n+1);
			ll ss=0;
			for(ri j=1;j<=L;++j)ss+=a[id[j]]+b[id[j]];
			for(ri j=L+1;j<=n;++j)q1.push(a[id[j]]),q2.push(b[id[j]]);
			for(ri j=L+1;j<=K;++j)ss+=q1.top()+q2.top(),q1.pop(),q2.pop();
			ans=max(ans,ss);
			while(q1.size())q1.pop();
			while(q2.size())q2.pop();
		}
		cout<<ans<<'\n';
	}
}
namespace subtask2{
	ll f[2][151][151][151];
	inline bool chkmax(ll&a,const ll&b){return a=a>b?a:b,1;}
	inline void Main(){
		int t=f[0][0][0][0]=0;
		for(ri i=1;i<=n;++i){
			t^=1;
			for(ri j=max(i+L-n,0);j<=K&&j<=i;++j)for(ri k1=max(j,i+K-n);k1<=K&&k1<=i;++k1)for(ri k2=max(j,i+K-n);k2<=K&&k2<=i;++k2){
				f[t][j][k1][k2]=0;
				j<i?((k1<i)&&(k2<i)&&chkmax(f[t][j][k1][k2],f[t^1][j][k1][k2]),
				(k1<i)&&(k2>0)&&chkmax(f[t][j][k1][k2],f[t^1][j][k1][k2-1]+b[i]),
				(k2<i)&&(k1>0)&&chkmax(f[t][j][k1][k2],f[t^1][j][k1-1][k2]+a[i])):0;
				(j>0)&&(k1>0)&&(k2>0)&&chkmax(f[t][j][k1][k2],f[t^1][j-1][k1-1][k2-1]+a[i]+b[i]);
			}
		}
		ll ans=0;
		for(ri i=L;i<=K;++i)ans=max(ans,f[t][i][K][K]);
		cout<<ans<<'\n';
	}
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	for(ri tt=read();tt;--tt){
		n=read(),K=read(),L=read();
		for(ri i=1;i<=n;++i)a[i]=read();
		for(ri i=1;i<=n;++i)b[i]=read();
		if(n<=150)subtask2::Main();
	}
	return 0;
}

现在考虑用费用流解决问题。
我们设第一个数列 1 1 1 ~ n n n对应编号为 p 1 p_1 p1 ~ p n p_n pn
第二个数列 1 1 1 ~ n n n对应编号为 q 1 q_1 q1 ~ q n q_n qn
那么可以这样连边:
( s , p i , 1 , a i ) , ( q i , t , 1 , b i ) , ( p i , q i , 1 , 0 ) (s,p_i,1,a_i),(q_i,t,1,b_i),(p_i,q_i,1,0) (s,pi,1,ai),(qi,t,1,bi),(pi,qi,1,0)
并限制总流量为 K K K
这样相当于是强制只能选 K K K对下标一样的,跟题意不符。
于是我们新建两个点 a , b a,b a,b
然后多连上这些边 ( p i , a , 1 , 0 ) , ( b , q i , 1 , 0 ) , ( a , b , K − L , 0 ) (p_i,a,1,0),(b,q_i,1,0),(a,b,K-L,0) (pi,a,1,0),(b,qi,1,0),(a,b,KL,0)
最后跑费用流就能拿到一个比较好看的分数。

现在考虑模拟费用流,发现我们要维护 3 3 3类堆,分别表示:

  1. a / b a/b a/b数列还剩下了那些位置没有被选过。
  2. 对于 a / b a/b a/b数列,有哪些位置在 b / a b/a b/a数列中已经被选过且在 a / b a/b a/b数列中未被选过。
  3. 有哪些位置在 a , b a,b a,b中都未被选过。

显然第一类点包含第二,三类点。

然后就可以模拟费用流啦。
每次如果 a → b a\rightarrow b ab这条边有流量就先流这条边,否则再讨论:

  1. a a a中选一个 2 2 2类点,从 b b b中选一个 1 1 1类点。
  2. b b b中选一个 2 2 2类点,从 a a a中选一个 1 1 1类点。
  3. a , b a,b a,b中选一对 3 3 3类点。

最后要注意退流的情况

100 p t s 100pts 100pts代码:

#include
#define ri register int
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef pair<int,int> pii;
typedef long long ll;
const int N=2e5+5;
int n,K,L,a[N],b[N],sta[N],Flow;
ll ans;
struct cmp_a{inline bool operator()(const int&x,const int&y){return a[x]<a[y];}};
struct cmp_b{inline bool operator()(const int&x,const int&y){return b[x]<b[y];}};
struct cmp_ab{inline bool operator()(const int&x,const int&y){return a[x]+b[x]<a[y]+b[y];}};
priority_queue<int,vector<int>,cmp_a>q1,p1;
priority_queue<int,vector<int>,cmp_b>q2,p2;
priority_queue<int,vector<int>,cmp_ab>q;
pii A[N],B[N];
inline void init(){
	while(q1.size())q1.pop();
	while(p1.size())p1.pop();
	while(q2.size())q2.pop();
	while(p2.size())p2.pop();
	while(q.size())q.pop();
	ans=0,Flow=0;
	sort(A+1,A+n+1),reverse(A+1,A+n+1);
	sort(B+1,B+n+1),reverse(B+1,B+n+1);
	for(ri i=1;i<=K-L;++i)ans+=A[i].fi+B[i].fi,sta[A[i].se]|=1,sta[B[i].se]|=2;
}
inline void Pop(){
	while(q1.size()&&(sta[q1.top()]&1))q1.pop();
	while(q2.size()&&(sta[q2.top()]&2))q2.pop();
	while(p1.size()&&(sta[p1.top()]^2))p1.pop();
	while(p2.size()&&(sta[p2.top()]^1))p2.pop();
	while(q.size()&&sta[q.top()])q.pop();
}
inline void update1(){
	int x=q1.top(),y=q2.top();
	--Flow;
	ans+=a[x]+b[y];
	if((sta[x]|=1)^3)p2.push(x);
	if((sta[y]|=2)^3)p1.push(y);
	Flow+=x^y?(sta[x]==3)+(sta[y]==3):1;
}
inline void update2(){
	int x,y,s1=0,s2=0,s3=0,detflow1=0,detflow2=0;
	if(p1.size())s1=a[p1.top()]+b[q2.top()];
	if(p2.size())s2=a[q1.top()]+b[p2.top()];
	if(q.size())s3=a[q.top()]+b[q.top()];
	int mx=max(max(s1,s2),s3);
	ans+=mx;
	if(s1==mx){
		x=p1.top(),y=q2.top();
		sta[x]|=1,sta[y]|=2;
		if(sta[y]==3)++Flow;
		else p1.push(y);
		return;
	}
	if(s2==mx){
		x=q1.top(),y=p2.top();
		sta[x]|=1,sta[y]|=2;
		if(sta[x]==3)++Flow;
		else p2.push(x);
		return;
	}
	if(s3==mx)x=q.top(),sta[x]=3;
}
int main(){
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
	for(ri tt=read();tt;--tt){
		n=read(),K=read(),L=read();
		for(ri i=1;i<=n;++i)sta[i]=0;
		for(ri i=1;i<=n;++i)A[i]=pii(a[i]=read(),i);
		for(ri i=1;i<=n;++i)B[i]=pii(b[i]=read(),i);
		init();
		for(ri i=1;i<=n;++i){
			switch(sta[i]){
				case 0:{q1.push(i),q2.push(i),q.push(i);break;}
				case 1:{q2.push(i),p2.push(i);break;}
				case 2:{q1.push(i),p1.push(i);break;}
				default:{++Flow;break;}
			}
		}
		while(L--){
			Pop();
			if(Flow){update1();continue;}
			update2();
		}
		cout<<ans<<'\n';
	}
	return 0;
}

你可能感兴趣的:(#,费用流,#,贪心,#,枚举)