1906E Merge Not Sort

本文介绍了一种解决未排序数列合并问题的方法,通过将问题转化为类似背包问题的动态规划,利用状态转移规则找出符合条件的数列分割,最终输出a和b数列的构造。
摘要由CSDN通过智能技术生成

题目链接

题意:

用类似归并排序的思路合并两个长度为n的数列ai和bi,但是两个数列没有排序,给出排序后长度为2*n的数列ci,问a和b数列可能的样子,不可能构造成功则输出-1。

思路:

想象归并的过程,一定是a数列拿若干个数,然后b拿若干个数,然后循环,每次拿数出现切换时(以a切换到b为例)一定是因为b数列这时候最前面的数比a数列这时最前面的数要小,而b最前面的数一定比a拿出去的数都大。

不妨假设切换到一个数列拿出若干个数直到需要切换到另一个数列之前的这个场景称为一个状态。那么第k个状态(假如是a拿出若干个数)下a拿出去的所有数都比b最前面的数要小,第k-1个状态下b拿出去的所有数都比a最前面的数要小,类推,因此第k个状态下的b最前面的那个数一定比b前面的数都要大,而且比a拿出去的所有数都要大,也就是说表现在合并后的数列c里,这个数比前面所有数都大。反过来,如果数列c里有一个数比前面所有数都大(因为是1~2n排列,所以不存在相等),那么给数的时候在这里就有可能发生转换。

把上面说的满足条件的数标记一下,第i个满足的数下标是idx[i],那么第i个满足的数连带着后面不满足的数就可以分给a或者b,长度为len[i]=idx[i+1]-idx[i]。我们把idx[i]到idx[i+1]-1的部分看成一块,假设一共分了cnt块,这样相当于给a和b分配这些块,使得a和b数列都恰好长度为n。

先看有没有解,也就是看看cnt块能不能凑出一个长度为n的数列给a,剩下的给b也是长度为n。爆搜是可以的,但是最坏情况要搜 2 c n t = 2 2 ∗ n 2^{cnt}=2^{2*n} 2cnt=22n次,有生之年是看不到结果了,考虑dp。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示使用前i个部分能否表示j,状态转移方程是 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ∣ d p [ i − 1 ] [ j − l e n [ i ] ] dp[i][j]=dp[i-1][j]|dp[i-1][j-len[i]] dp[i][j]=dp[i1][j]dp[i1][jlen[i]](len[i]表示第i块的长度)。这和01背包很像,可以直接优化掉第一维,得到状态转移方程 d p [ j ] = d p [ j ] ∣ d p [ j − l e n [ i ] ] dp[j]=dp[j]|dp[j-len[i]] dp[j]=dp[j]dp[jlen[i]],需要倒序枚举j,时间复杂度是 O ( c n t ∗ n ) O(cnt*n) O(cntn)。如果i跑完cnt后dp[n]是0,说明凑不出来,直接输出-1。

但是需要输出a和b的一种构造,考虑dp的时候把分配给a数列的块标记一下,也就是如果 d p [ j ] dp[j] dp[j] d p [ j − l e n [ i ] ] dp[j-len[i]] dp[jlen[i]]转移过来,说明 j j j长度要加上一个 l e n [ i ] len[i] len[i] j − l e n [ i ] j-len[i] jlen[i]转移过来,设置一个数组 l s t [ j ] lst[j] lst[j],表示 d p [ j ] dp[j] dp[j]拿了哪个部分。跑一遍dp,终态是 d p [ n ] dp[n] dp[n],然后通过lst一直找上一个状态,找到 d p [ 0 ] dp[0] dp[0]为止,再把这些状态拿的块标记一下,就是a数组拿的块,最后输出即可。

code:

#include <iostream>
#include <cstdio>
#define reg register
using namespace std;
const int maxn=1005;

int n,c[maxn<<1];
int len[maxn<<1],cnt;
int idx[maxn<<1];
bool vis[maxn<<1];
int dp[maxn],lst[maxn];


int main(){
	cin>>n;
	for(int i=1,tmp=0,t=0;i<=(n<<1);i++){//前i-1个数中 tmp最大的 t下标 
		cin>>c[i];
		if(c[i]>=tmp){
			idx[++cnt]=i;
			len[cnt-1]=idx[cnt]-idx[cnt-1];
			tmp=c[i];
			t=i;
		}
	}
	len[cnt]=(n<<1)+1-idx[cnt];
//	for(int i=1;i<=cnt;i++)
//		cout<<idx[i]<<" ";
//	puts("");
//	for(int i=1;i<=cnt;i++)
//		cout<<len[i]<<" ";
//	puts("");
	
	dp[0]=1;lst[0]=0;
	for(int i=1;i<=cnt;i++){
		for(int k=n;k>=len[i];k--){
			if(!dp[k] && dp[k-len[i]]){
				dp[k]=dp[k-len[i]];
				lst[k]=i;//用len[i]推过来 
			}
		}
		if(dp[n])break;
	}
	if(!dp[n]){
		cout<<-1;
		return 0;
	}
	
	for(int k=n,i;k;k-=len[i]){
		i=lst[k];
		vis[i]=true;
	}
	for(int i=1;i<=cnt;i++)
		if(vis[i])
			for(int j=0;j<len[i];j++)
				printf("%d ",c[idx[i]+j]);
	puts("");
	for(int i=1;i<=cnt;i++)
		if(!vis[i])
			for(int j=0;j<len[i];j++)
				printf("%d ",c[idx[i]+j]);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值