[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: 子序列是指从一个序列中取出任意数量的元素,而不改变它们在原序列中的相对顺序所得到的新序列。换句话说,子序列是原序列的一个部分,可以是连续或非连续的。 对于给定的一个序列,请你求出它的子序列的个数。 解题思路: 我们可以使用动态规划的思想来解决这个问题。假设原序列的长度为n。首先,我们定义一个长度为n的数组dp,其中dp[i]表示以第i个元素结尾的子序列的个数。 初始化时,dp数组的所有元素都为1,因为每个元素本身也是一个子序列。 然后,我们从第二个元素开始遍历原序列。对于当前遍历到的元素,我们需要计算以它结尾的子序列的个数。遍历到第i个元素时,我们需要向前遍历前面的元素,若前面的某个元素小于第i个元素,则第i个元素可以接在这个元素的后面,形成一个新的子序列。此时,我们可以利用dp数组来直接求出以前面的这个元素结尾的子序列的个数,并将它们累加到dp[i]中。 最后,我们将dp数组中所有元素的值相加,即可得到原序列的子序列的个数。 例如,对于序列1 2 3 4,其子序列的个数为15,具体的子序列是(1)、(2)、(3)、(4)、(1 2)、(1 3)、(1 4)、(2 3)、(2 4)、(3 4)、(1 2 3)、(1 2 4)、(1 3 4)、(2 3 4)、(1 2 3 4)。 这就是使用动态规划求解子序列个数的方法。 希望对你有帮助! ### 回答2: 子序列是指从给定序列中删除若干个元素后所得到的序列,而被删除的元素的顺序保持不变。例如,对于序列[1, 2, 3, 4, 5],它的子序列可以为[1, 2, 3]、[2, 4, 5]等。那么现在我们来解答关于子序列NOI教师培训试题。 试题:给定一个长度为n的正整数序列a,若存在一个长度为m的序列b(b中元素值可以不连续)是a的子序列,并且b满足b中各个元素之和可以整除k,输出序列b的最大长度m。 解答: 首先,我们可以使用动态规划的思想来解决这个问题。定义一个dp数组,dp[i]表示以第i个元素结尾的子序列的最大长度。初始化dp数组的所有元素为1。 然后,我们遍历序列a,对于每个元素a[i],再遍历它之前的元素a[j](0 <= j < i),如果a[i]可以整除k,说明可以将a[j]添加到以a[i]结尾的子序列中,此时更新dp[i] = max(dp[i], dp[j] + 1)。最后,找出dp数组中的最大值,即为题目所求的结果。 例如,对于序列a = [1, 2, 3, 4, 5],k = 3,执行上述算法得到dp数组为[1, 1, 1, 1, 2],最大值为2,因此输出结果为2。 该算法的时间复杂度为O(n^2),在n较小的情况下可以接受。如果希望进一步优化时间复杂度,可以考虑使用动态规划+哈希表的方法,将时间复杂度降低到O(n)。 以上就是关于NOI教师培训试题子序列的解答,希望能对您有所帮助! ### 回答3: 子序列是指从一个给定的序列中选择出若干个元素,这些元素在原序列中保持相对顺序不变,但不一定连续。例如,对于序列1 2 4 3,它的子序列可以是1 4、2 3、1 2 3、4等。 求一个序列的最长递增子序列是一个经典问题。给定一个整数序列,我们要找出一个最长的递增子序列,其中递增表示:对于任意的i和j,如果i < j,则ai < aj。例如,对于序列2 1 4 3,它的最长递增子序列是1 3,长度为2。 解决这个问题的动态规划算法可以描述如下: 1. 创建一个辅助数组dp,dp[i]表示以第i个元素结尾的最长递增子序列的长度。 2. 初始化dp数组,将dp的所有元素都初始化为1,表示每个元素本身就是一个递增子序列,长度为1。 3. 从第2个元素开始遍历原序列,依次计算每个元素结尾的最长递增子序列的长度。 4. 对于每个元素,从它之前的元素中找到比它小的元素,如果找到,就更新dp[i]为dp[j]+1,表示以当前元素结尾的最长递增子序列长度增加1。 5. 遍历完整个序列后,dp数组中的最大值即为原序列的最长递增子序列的长度。 上述算法的时间复杂度是O(n^2),其中n是序列的长度。还有其他更优化的算法,可以将时间复杂度降到O(nlogn),例如使用二分查找或贪心算法。 对于NOI教师培训试题,子序列问题是一个较为常见的题型,可以使用上述动态规划算法进行求解。在解题过程中,需要注意理解子序列的含义,以及动态规划算法的思想。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值