圆桌会议
题目描述
有N个人顺时针围在一圆桌上开会,他们对身高很敏感.因此决定想使得任意相邻的两人的身高差距最大值最小.如果答案不唯一,输出字典序最小的排列,指的是身高的排列.
输入格式 1864.in
多组测试数据。第一行:一个整数ng, 1 <= ng <= 5.表示有ng组测试数据。
每组测试数据格式如下:
第一行: 一个整数N, 3 <= N <= 50
第二行, 有个N整数,第i个整数表示第i个人的身高hi, 1<=hi<=1000.按顺指针给出N个人的身高.空格分开。
输出格式 1864.out
字典序最小的身高序列,同时满足相邻的两人的身高差距最大值最小。共ng行,每行对应一组输入数据。
输入样例 1864.in
2
5
1 3 4 5 7
4
1 2 3 4
输出样例 1864.out
1 3 5 7 4
1 2 4 3
话说网上关于这题的做法多种多样,怎么做的都有……
结合老师兴奋的讲解,我决定对这几种做法都进行一次总结。因为,都、很、好,很有借鉴意义。
这一题的做法大致可以分成四种,贪心、DP、二分+贪心、二分+网络流。
先说一个不难得出又十分重要的大前提吧。我们要使得身高差和字典序更优,那么身高序列一定是单峰的(即左边是先单调上升,右边再单调下降)。至于为什么,留给读者自行思考。[滑稽]
另外,做法一、二所有的假设、操作都是基于从小到大排序的。
一、 二分+(玄学)贪心
题目中要求身高差最大的最小。根据多年经验想到二分答案。二分出一个最大的身高差,然后进行验证。
可是如何验证呢?由于时间复杂度较大,枚举自然是不可能的。其实显而易见,如果以最优的排列方式都无法满足二分出的身高差mid,那么才能证明这个mid是不合法的。
那么,如何做最优的排列方式呢?
下面是zyc大佬的玄学贪心做法:
我们先考虑左边这一段的放法。很显然,左边放什么,对右边能放什么是有影响的。为了降低这个影响,对于左边的序列,就让他们的身高差在满足mid的情况下,尽可能大。
然后,剩余的都放到右边去。
如果这样放左边,都放不到a[n](峰顶), 那么这个mid一定不合法,lo=mid。如果这样放右边,右边某身高差都超过mid,那么mid也一定不合法,lo=mid。
如果上述两种情况都不满足,则mid合法,且答案可能更小,hi=mid。
最后,hi即为最小的最大身高差。
但是,上述的放法,很显然并不是最小字典序的放法。对于最小字典序来说,左边的递增序列的差当然要尽可能小。(为什么不能直接做左边呢?因为尽可能取小的,很可能导致整个序列不合法)。因此,应用贪心,让右边的递减序列的身高差 合法时尽可能大。剩下的按照升序放左边即可。
是不是特别玄学……贪心博大精深……
zyc的神奇代码如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <vector>
#include <queue>
using namespace std;
const int INF=0x3FFFFFFF;
bool vis[60];
int down[60];
int cur;
int h[60];
int main()
{
int t;
cin>>t;
while (t--)
{
int n;
cin>>n;
for (int i=1; i<=n; i++) cin>>h[i];
sort(h+1,h+n+1);
h[n+1]=INF;
int L=0,R=h[n]-h[1];
while (L+1<R)
{
int mid=(L+R)/2;
for (int i=1; i<=n; i++) vis[i]=0;
int maxh=h[1];
vis[1]=1;
int tail=1;
while (tail<n)
{
while (h[tail+1]-maxh<=mid) tail++;
if (vis[tail]) break;
vis[tail]=1;
maxh=h[tail];
}
if (tail<n) L=mid;
else
{
vis[1]=vis[n]=0;
bool p=1;
for (int i=1; i<n; i++)
if (!vis[i])
for (int j=i+1; j<=n; j++)
if (!vis[j])
{
if (h[j]-h[i]>mid) p=0;
break;
}
if (p) R=mid;
else L=mid;
}
}
for (int i=1; i<=n; i++) vis[i]=0;
cur=1,down[1]=1;
int tail=2;
while (tail<n)
{
while (h[tail+1]-h[down[cur]]<=R) tail++;
vis[tail]=1;
down[++cur]=tail;
}
vis[1]=vis[n]=0;
for (int i=1; i<=n; i++)
if (!vis[i]) cout<<h[i]<<" ";
for (int i=n; i>0; i--)
if (vis[i]) cout<<h[i]<<" ";
cout<<endl;
}
return 0;
}
二、 玄学贪心
这一种做法更加强大,直接贪心……
首先,求最小身高差。比如说有:2 5 7 9 15 19 28这些数。如果只是2 5,那身高差只能是3。现在运行到7。怎么做能让7插进去之后最优呢?对于5来说,7放到它的右边是最优的,但实际上这样,首末的差距又会变大。因此最优策略应是将其放在2和5的中间。再运行到9,此时显然应放在7和5中间,序列变成2 5 9 7……
这样类推下去不难发现,每插进一个数,都将其放入前面序列中的最大值和第二大值之间,就能获得最优解。为什么呢?你不管放在哪里,都会与它左右两边的数形成新的差距。这个新差距自然是越小越好。怎么办? 跟它差距最小的,只能是前面序列中的前两个最大值。因此,此求最小身高差的贪心策略是正确的。证明完毕。
通过上述的贪心方法,我们可以求出最小身高差ans。但是上述插数的方法却未必是最优字典序,还应再求。
还是上面那个例子:2 5 7 9 15 19 28,可以求出最小身高差为13。根据字典序的规则,我们让小的数尽可能留在前面(当然最好是按照升序排列所有的数)。但是,由于有ans的限制,有时候不得不进行调整。
定义cur, i,cur初始化为1。 i从1开始进行枚举,如果存在a[i]-a[cur]>ans, 则表明,i后面的值跟a[cur]匹配也一定会超过ans ,因而需要一个数去后面(下降部分),跟a[cur]匹配。为了保证字典序最优,这个去后面的值为a[i-1]。然后,将cur移到i的位置,继续枚举i……
在执行上述过程的途中,将要去后面的标记一下。最后,按升序输出前面的值,按降序输出后面的值,即为答案。
正确性:上述的方法,既可以保证答案的合法,又可以使得最少的值去后坡段,因而可以求出最优的字典序。
这个玄学贪心,太妙了~~~~~~~~~
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=55;
int g,n,a[MAXN],ans,v[MAXN],result[MAXN];
int main()
{
cin>>g;
while(g--)
{
memset(v,0,sizeof(v));
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
ans=a[2]-a[1];
for(int i=3;i<=n;i++) ans=max(ans,a[i]-a[i-2]);
int cur=1;
for(int i=1; i<=n; i++)
if(a[i]-a[cur]> ans)
{
i--;
v[i] = true;
cur= i;
}
int head=0,tail=n;
for(int i=1;i<=n;i++)
if(!v[i])
result[++head] = a[i];
else
result[tail--] = a[i];
for(int i=1;i<=n;i++)
cout<<result[i]<<" ";
cout<<endl;
}
return 0;
}
三、 双路DP
由于这是一个单峰,在山峰的两边,可以看做是从大到小,一个一个放的,具有明显的阶段性,求最小身高差的时候,就可以用双路DP解决。
(此处从大到小排序)
(这种双路DP,跟之前的变形合唱队列是类似的)
设f[i][j]表示左坡段的最底值为a[i],右坡段的最底值为a[j]时的最小的最大身高差距。
首先f[1][0]=0(即只有峰顶的时候)
然后进行状态转移。第i个数可以放左边,也可以放右边,道理是一样的。那就讨论放左边的吧。
考虑一下,前一个状态(即子问题)是什么?第i-1个,可能放在左边,也可能放在右边,如果放在左边,则子问题为f[i-1][k];如果放右边,则子问题为f[k][i-1](0<=k<=i-2)。将a[i]放进序列中,势必会产生一个新的差距,最大新差距为max(a[i-1]-a[i],a[k]-a[i])。然后拿它跟子问题的答案取MAX, 即为这种插法的身高差。将所有插法的身高差取一个min,就是f[i][j]了。
最后f[n][k](0<=k<=n-1)(f[k][n]答案跟它一样)的最小值即为最小身高差。
好了,现在再来弄字典序的问题。很黄很暴力……
我们现在从第一个开始放起。从头到尾,都希望尽可能小。第一个自然是最小值,后面从2到n, 对于每个位置,在身高差限制内,都从小到大一一尝试(前提是它没有被放),对于每个尝试,都在剩下的未做序列中,做双路DP,看是否能满足已知的最优身高差。如果能够满足,则停止枚举,当前的值,即为答案中这个位置应放的值。
时间复杂度:O(N^4)
此处不附代码了,这题磨死我了,没心情写了。
四、 重重重头戏——二分+网络流
写做法?算了吧。昨天yy了一个二分图匹配的方法,跟ywq雷同,写完自己都觉得不对。
然后我个吃瓜群众静静看着教练争论,表示听得不太懂。
网络流蒟蒻默默吃瓜。此处留坑待填。
不过话说网络流是万能的,A+B problem都能做。[滑稽]