[NOI2019]序列

本文详细解析了NOI2019中一道涉及序列的难题,通过分析64分和100分的解题策略,讲解如何运用贪心算法和网络流解决复杂匹配问题。64分解决方案主要通过最大费用最大流,100分则需要处理更多细节,包括模拟费用流并结合不退流和退流情况,以及处理特殊情况以优化答案。实现过程中涉及到多个堆的维护,同时强调了题目对实现细节的高要求。
摘要由CSDN通过智能技术生成

题目链接
听大佬讲题,太神辣(
一道思维和细节兼顾的毒瘤题。


题目大意:我认为题意讲的很清楚。


64pts

直接思考正解是不太现实的,先从部分分入手。
观察到前64分与后36分有较大差距,可以从这里切入。
一般来说,对于匹配题基本都是这几个套路:DP、贪心、网络流、二分。
DP自然可以64pts,然而没法优化;
反悔贪心可做,然而思路没那么自然好想;
二分……据说可以做,但是不会/kk;
之后就是网络流。
对于题目,我们可以建立出如下的图:
在这里插入图片描述

然后跑最大费用最大流,64pts就到手了。


100pts

考试时64pts就可以跑了,不过这是练习,要争取拿到100pts。
考虑模拟一下费用流,分析情况。

  • 若此时边 ( a 0 , b 0 ) (a_0,b_0) (a0,b0) 仍有盈余流量,则此次增广肯定会选取左右两端未被选取的最大权值的点;

否则,分两种状况:

  • 不退流

不退流的话,选取权值最大的一对 ( a x , b x ) (a_x,b_x) (ax,bx) 就好了,增加费用为 a x + b x a_x+b_x ax+bx

  • 退流

假设选择流 ( S − > a x − > a 0 − > b 0 − > b y − > T ′ − > T ) (S->a_x->a_0->b_0->b_y->T'->T) (S>ax>a0>b0>by>T>T) 退流时有两种情况:选择两个点 a z a_z az b x b_x bx,或选择两个点 a y a_y ay b z b_z bz
这里以选择 a z a_z az b x b_x bx 为例:
选择了后,原来一个流变为两个流: ( S − > a x − > b x − > T ′ − > T ) (S->a_x->b_x->T'->T) (S>ax>bx>T>T) ( S − > a z − > a 0 − > b 0 − > b y − > T ′ − > T ) (S->a_z->a_0->b_0->b_y->T'->T) (S>az>a0>b0>by>T>T)
此时边 ( a 0 , b 0 ) (a_0,b_0) (a0,b0) 流量仍为 K − L K-L KL,总费用增加了 a z + b x a_z+b_x az+bx,找到可以使增加费用最大的流即可;
另一种情况同理,总费用增加了 a y + b z a_y+b_z ay+bz
将不退流和退流的两种情况结合起来,就可以得到本次增广增加的贡献。
开若干个堆维护,总共增广 K K K 次就能得到最终答案。


然而这道题除了足够的思维方面,实现细节也是多到离谱(毕竟NOI题)
为了维护数据,我们使用五个堆 a , b , a ′ , b ′ , a b a,b,a',b',ab a,b,a,b,ab

  • a , b a,b a,b 表示未匹配过的点;
  • a ′ , b ′ a',b' a,b 表示自身未匹配,但所对的点已经匹配过的点;
  • a b ab ab 表示一对均为匹配过的点。

然而把这些都维护完后,交上去WA一大片。因为还有一种特殊的状态:

  • 从源点流出了两条流 a x − > b y a_x->b_y ax>by a z − > b x a_z->b_x az>bx
  • 此时可以将两条流改为 a x − > b x a_x->b_x ax>bx a z − > b y a_z->b_y az>by,这样就省下了 ( a 0 , b 0 ) (a0,b0) (a0,b0) 的一点流量,可以做出更多的贡献。

具体的实现:设立一个调整函数,在退流和直接选取时检查是否出现特殊状况,如果有,就将 ( a 0 , b 0 ) (a_0,b_0) (a0,b0) 的流量减去一。
到此此题就结束了_(:з」∠)_
总的来说作为NOI的D1T3,还是偏向简单的。

Code

#include<bits/stdc++.h>
#define N 200010
using namespace std;
priority_queue<pair<long long,int>>a0,a1,b0,b1,ab;//a0:未匹配的a,a1:b已匹配的a,ab:均未匹配的一对
int a[N],b[N],mat[2][N],used[N],t,n,k,l,cnt,flag,pos1,pos2;
long long ans,mx;
void del(priority_queue<pair<long long,int>> &x,int id)//id(0~4):a0,a1,b0,b1,ab
{
	if(x.empty())return;
	if(id<4)
		while(mat[id>1][x.top().second]>0)
		{
			x.pop();
			if(x.empty())return;
		}
	else while(used[x.top().second])
		{
			x.pop();
			if(x.empty())return;
		}
}
void adjust(int x)//松弛
{
	if(mat[0][x]&&mat[1][x]&&mat[0][x]!=x&&mat[1][x]!=x)
	{
		mat[0][mat[1][x]]=mat[0][x];
		mat[1][mat[0][x]]=mat[1][x];
		if(mat[0][x]==mat[1][x])cnt++;
		mat[0][x]=mat[1][x]=x;
		cnt++;
	}
}
void match0()//直接取两个未被匹配的 
{
	del(a0,0);del(b0,2);
	long long u=a0.top().first,v=b0.top().first;
	int x=a0.top().second,y=b0.top().second; 
	mat[0][x]=y;
	mat[1][y]=x;
	if(x!=y)cnt--;
	used[x]=used[y]=1;
	adjust(x);adjust(y);
	if(!mat[1][x])b1.push(make_pair(b[x],x));
	if(!mat[0][y])a1.push(make_pair(a[y],y));
	ans+=u+v;
}
void match1()//取一对出来匹配
{
	del(ab,4);
	if(ab.empty())return;
	long long u=ab.top().first;
	int x=ab.top().second;
	if(u>mx)
	{
		flag=1;
		mx=u;
		pos1=pos2=x;
	}
}
void adjust1()
{
	mat[0][pos1]=mat[1][pos2]=pos1;
	ab.pop();
	used[pos1]=1;
}
void match2()//取a1,b0出来匹配
{
	del(a1,1);del(b0,2);
	if(a1.empty()||b0.empty())return;
	long long u=a1.top().first,v=b0.top().first;
	int x=a1.top().second,y=b0.top().second;
	if(u+v>mx)
	{
		flag=2;
		mx=u+v;
		pos1=x;
		pos2=y;
	}
}
void adjust2()
{
	if(mat[1][pos1]==pos2)cnt++;
	mat[0][mat[1][pos1]]=pos2;
	mat[1][pos2]=mat[1][pos1];
	mat[0][pos1]=mat[1][pos1]=pos1;
	used[pos1]=1;used[pos2]=1;
	a1.pop();b0.pop();
	a1.push(make_pair(a[pos2],pos2));
	adjust(pos2);
}
void match3()//取a0,b1出来匹配
{
	del(a0,0);del(b1,3);
	if(a0.empty()||b1.empty())return;
	long long u=a0.top().first,v=b1.top().first;
	int x=a0.top().second,y=b1.top().second;
	if(u+v>mx)
	{
		flag=3;
		mx=u+v;
		pos1=x;
		pos2=y;
	}
}
void adjust3()
{
	if(mat[0][pos2]==pos1)cnt++;
	mat[1][mat[0][pos2]]=pos1;
	mat[0][pos1]=mat[0][pos2];
	mat[0][pos2]=mat[1][pos2]=pos2;
	used[pos2]=1;used[pos1]=1;
	a0.pop();b1.pop();
	b1.push(make_pair(b[pos1],pos1));
	adjust(pos1);
}
void init()
{
	while(!a0.empty())a0.pop();
	while(!a1.empty())a1.pop();
	while(!b0.empty())b0.pop();
	while(!b1.empty())b1.pop();
	while(!ab.empty())ab.pop();
	memset(mat,0,sizeof mat);
	memset(used,0,sizeof used);
	ans=0;
}
int main()
{
	cin>>t;
	while(t--)
	{
		init();
		scanf("%d%d%d",&n,&k,&l);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int i=1;i<=n;i++)scanf("%d",&b[i]);
		for(int i=1;i<=n;i++)
		{
			a0.push(make_pair(a[i],i));
			b0.push(make_pair(b[i],i));
			ab.push(make_pair(a[i]+b[i],i));
		}
		cnt=k-l;
		while(k--)
		{
			if(cnt)
			{
				match0();
				continue;
			}
			flag=mx=0;
			match1();
			match2();
			match3();
			switch(flag)
			{
				case 1:adjust1();break;
				case 2:adjust2();break;
				case 3:adjust3();break;
			}
			ans+=mx;
		}
		cout<<ans<<'\n';
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值