目录
题目:
解题思路:
同大部分的解题思路一样,我们采用贪心算法,对于每一个数,我们给它剩下还可用的数字中的比它小的数字的最大值。但是在这里处理的时候,我们要从后往前处理。让我来简单说说这种贪心的可行性。
那就构造一个一组数据数据说吧,假设给定n=6, 数字序列为3 2 6。 因为我们需要构造出字典序最小的组合,所有我们要尽可能地把大的数字排在后面, 那这样的话我们是不是可以直接从前面开始,每次都分配可用数字种最小的那个数不就好了吗。可事实这样是不行,在3 2 6中,按上面的想法,把1给了3,但是在2的时候就只能选4和5,显然,这样并不符合题意,因为4,5均大于3。
重新再回到我们的可行的贪心算法中, 我们从后往前进行,每次分配给当前数字的都是可选数字中的比它小的数子中的最大值。那为什么每次给的都是最大值呢?这当然是为了构造出字典序最小的组合了。如6,剩下可选的数字是1 4 5,那就把5给它,来到2 ,剩下的是1 4,那就只能给它1,把4 给3,那就是3 4 1 2 5 6 这个序列了,这就是我们的答案。为什么这样可行使我们最关注的问题,我就简单地说说我的想法吧,当我们用掉一个数字后,我们最关心的就是会不会影响到前面的数字,导致前面的某个数字没有数字可以分配这种情况的出现(就像上面我们说的把1给3,导致2无数字可用)。这里我们重新构造一组数据,给定n=6,数字序列为6 2 4,现在考虑最后一个数字4,可选的数字有1和3,选取较大的数字3,4后面(这里4后面没有)的已经确定,我们只需要考虑会不会影响到在4前面的数字,在4前面的有比4小的数字2,也有比4大的数字6。对于比4大的数字,我们选取任何可选数字都不会出现它无数字可选的情况出现,因为我们选取的数字是比4小的,那也一定会比6小。但是对与前面比4小的数字2,可选数字1比2小,3比2大,就会出现这两个情况。选1给4后,就会导致2无数字可选了。但是我们的策略中每次都是选取的最大值,不论怎样我么都是要给4一个数字的,选取第二大的可能就导致第一大的数字在前面无法分配,但是选取最大的一定不会造成这样的影响(除非本组数据本就是无解的)。这样一定不会影响到前面2的选择,同时还满足了最小字典序的要求。
好,简单看完了上面的,那我们的再简单说说什么情况下数据无解,像这样n=6,数字序列1 2 3这样显然无解,还有就是有重复数字。在代码中我们处理无解的情况也非常简单,我们处理到一个数字无数字可选的时候,就会返回最大值是0,代表无数字可选,另外,针对重复数字,那我们在最后一定还会剩下可选数字,也就是说,剩余数字的最大值不是0。这些在线段树中都非常好处理。
AC代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<numeric>
using namespace std;
//贪心+线段树维护区间最大值,时间复杂度o(2n-1*log(2n-1)),主要时间在建立线段树上
int dp[400010]; //线段树维护一个区间最大值
int Tree[800010]={0};
void BuildTree(int left,int right,int index) //建线段树
{
if(left>right) return;
if(left==right)
{
Tree[index]=dp[left];
return;
}
int mid=(left+right)>>1;
BuildTree(left,mid,index*2);
BuildTree(mid+1,right,index*2+1);
Tree[index]=max(Tree[index*2],Tree[index*2+1]);
}
int findans(int L,int R,int left,int right,int index) //在left到right区间里寻找区间[L,R]的最大值
{
if(R<left||L>right) return 0;
if(left>right) return 0;
if(left>=L&&right<=R) return Tree[index];
int ans1=0,ans2=0;
int mid=(left+right)>>1;
ans1=findans(L,R,left,mid,index*2);
ans2=findans(L,R,mid+1,right,index*2+1);
return max(ans1,ans2);
}
//修改,将线段树叶子值为target的节点修改为0,表示已经使用过该数字,递归更新区间最大值
void modify(int left,int right,int target,int index)
{
if(left>right) return;
if(left==right&&Tree[index]==target)
{
Tree[index]=0;
return;
}
int mid=(left+right)>>1;
if(target<=mid) modify(left,mid,target,index*2);
if(target>mid) modify(mid+1,right,target,index*2+1);
Tree[index]=max(Tree[index*2],Tree[index*2+1]);
}
int main()
{
int t,n,a;
cin>>t;
int nums[200010],ans[200010];
while(t--)
{
scanf("%d",&n);
iota(dp+1,dp+n+1,1); //填充数字1....n
for(int i=1;i<=n/2;i++)
{
scanf("%d",nums+i);
dp[nums[i]]=0; //将nums[i]这个数字记成0,区间最大值中就不会出现nums[i],表示不可用
}
BuildTree(1,n,1); //建树
bool flag=true;
for(int i=n/2;i>=1;i--)
{
ans[i]=findans(1,nums[i]-1,1,n,1); //寻找比它小的最大值
if(ans[i]==0) //返回0表示没有可用的数字,那么就没有这样一个组合
{
flag=false;
break;
}
modify(1,n,ans[i],1); //将得到的最大值使用后标记为0,后序就用不了了
}
if(flag==false||Tree[1]!=0)
{
cout<<-1<<endl;
continue;
}
for(int i=1;i<=n/2;i++)
{
if(nums[i]<ans[i])
{
cout<<nums[i]<<" "<<ans[i]<<" ";
}
else
{
cout<<ans[i]<<" "<<nums[i]<<" ";
}
}
cout<<endl;
}
return 0;
}
感谢您的观看。