题目链接:点击查看
题目大意:给出两个长度为 n 的数列记为 a 和 b,现在 a 的数列固定不动,问如何对数列 b 进行排列,可以使得:
- b[ i ] > a[ i ] (严格大于)的位置尽可能多
- 在满足上述要求的前提下,b 的排列字典序最大
题目分析:如果不考虑第二个约束的话,那就是一个非常简单的贪心问题了,贪心策略如下:因为最终需要的是 a 中的每个元素和 b 中每个元素一一对应的一个结果,所以只需要考虑其相对位置,将两个数组分别排序,然后用双指针一个去枚举 a 中的元素,另一个贪心从 b 中寻找元素与其匹配即可
如果再考虑上第二个约束该如何去做呢,因为 n 只有 5e3 的级别,可以考虑正向遍历一遍每个元素去确定 ans[ i ] 的值,此时不难想到一个 n^3 的做法了:
- 首先求出不受约束 2 限制的答案,记为 res
- 第一层枚举 i ,表示当前正在确定 ans[ i ] 的值
- 第二层枚举 j ,表示第 i 个位置放置 b 中的第 j 个元素 ,即 ans[ i ] = b[ j ]
- 最后一层循环去检查第 ans[ i ] = b[ j ] 时的答案,最后从贡献为 res 的 b[ j ] 中选出最大的 b[ j ] 放在 ans[ i ] 即可
考虑优化时间复杂度,第一层枚举的 i 无法优化,第三层检查的循环也不好优化,所以突破点就在第二层枚举的 j,我们先暂时搁置一下
到这里可能会有疑惑了,因为最开始说的,贪心策略去求解答案的话,是需要进行排序的,所以上述算法的时间复杂度不应该是 n^3*logn 的嘛,其实并不需要每次都排序,只需要初始时额外维护一个递增的数组 aa 和数组 bb,每次操作完位置 i 后将 a[ i ] 和 b[ j ] 中相应的元素删除掉即可,时间复杂度不过也才 O( n ) ,这样就将每次贪心求解答案的时间控制在了 O( n )
继续讨论如何优化掉第二层的 j,假设第一层枚举到了 i,此时 a 数组中对应的元素是 a[ i ] ,将 bb 数组(有序)中的元素分成两部分:
- 小于等于 a[ i ] 的
- 大于 a[ i ] 的
不难看出两个部分中的 bb 元素分别都是具有单调性的,具体指的是,对于情况一来说,如果 bb[ i ] 放在这里合适,那么 bb[ i + 1 ] 放在这里也是合适的,对于情况二来说,如果 bb[ i ] 放在这里合适,那么 bb[ i - 1 ] 放在这里也是合适的,如此就可以将枚举的时间复杂度优化成二分了,最后总的时间复杂度为O( n^2logn )
代码:
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<list>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=5e3+100;
int a[N],b[N],n;
vector<int>bb,aa;
int cal(int ban1,int ban2)//去掉aa[ban1]和bb[ban2]后的结果
{
int ans=0;
int i=0,j=0;
while(i<aa.size()&&j<bb.size())
{
if(i==ban1)
{
i++;
continue;//检查一下是否越界
}
if(j==ban2)
{
j++;
continue;//同上
}
if(aa[i]<bb[j])
{
ans++;
i++;
}
j++;
}
return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
aa.push_back(a[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%d",b+i);
bb.push_back(b[i]);
}
sort(aa.begin(),aa.end());
sort(bb.begin(),bb.end());
int ans=cal(-1,-1);
for(int i=1;i<=n;i++)
{
int pos1=-1;//定位,aa[pos1]=a[i]
for(int j=0;j<aa.size();j++)
if(aa[j]==a[i])
pos1=j;
int l=upper_bound(bb.begin(),bb.end(),a[i])-bb.begin(),r=bb.size()-1,mark=-1;
while(l<=r)
{
int mid=l+r>>1;
if(cal(pos1,mid)==ans-1)
{
mark=mid;
l=mid+1;
}
else
r=mid-1;
}
if(mark==-1)
{
int l=0,r=upper_bound(bb.begin(),bb.end(),a[i])-bb.begin()-1;
while(l<=r)
{
int mid=l+r>>1;
if(cal(pos1,mid)==ans)
{
mark=mid;
l=mid+1;
}
else
r=mid-1;
}
}
printf("%d ",bb[mark]);
ans-=(bb[mark]>aa[pos1]);
aa.erase(aa.begin()+pos1);
bb.erase(bb.begin()+mark);
}
return 0;
}