【题目链接】http://acm.hdu.edu.cn/showproblem.php?pid=5493
【解题报告】
比赛的时候并没有意识到这道水题的本质TAT,一度在想是不是要用2-SAT做,真是too young too simple.
这道题目有一个关键点就是,什么样的数据会impossible呢?
当且仅当N<i+K//i为一个人在n个人中的位置(预先排序),k为比他高的人数
为什么其他情况一定有解呢?假设有从低到高的一个排列,那么我们从高到低开始调整每个人在队列中的位置,将第i个人插在比他高的N-i个人之中即可。
那么我们只需要保证插完之后得到的序列字典序最小。如何保证这个输出方案呢?贪心的想,我们需要使低个尽可能站在前面,所以从低到高的第i个人,他可以让他的左边有k个高个,或者左边有N-i-k个高个,那么我们应当让他左边有尽可能少的高个(这样他才能站在前面)。所以应该取
num=min( k, N-i-k ),表示i左边有num个高个。
到了这里只剩下一个问题,就是如何插入,这一步可以用线段树来维护每个区间里还剩的空位的个数,每一步插入操作复杂度为O(logN),总时间复杂度就是O(NlogN)。
【参考代码】
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
struct node{
int h,k;
}P[100000+10];
int cmp( node A, node B ){ return A.h<B.h; }
int N;
int tree[100000*4];
int pos[100000+10];
void build( int o, int l, int r )
{
if(l==r) { tree[o]=1; return; }
int mid=l+(r-l)/2;
build(o*2, l, mid );
build(o*2+1, mid+1, r );
tree[o]=tree[o*2]+tree[o*2+1];
}
void update( int num, int k, int o, int l, int r )
{
if( l==r )
{
pos[l]=k;
tree[o]=0;
return;
}
int mid=l+(r-l)/2;
if( num<tree[o*2] )update( num, k, o*2, l, mid );
else update( num-tree[o*2], k, o*2+1, mid+1, r );
tree[o]=tree[o*2]+tree[o*2+1];
}
int main()
{
int T,kase=0;
cin>>T;
while( T-- )
{
memset( tree, 0, sizeof(tree) );
scanf("%d",&N);
for( int i=1; i<=N; i++ )scanf("%d%d",&P[i].h,&P[i].k);
sort( P+1, P+1+N, cmp );
build( 1, 1, N );
bool ok=true;
for( int i=1; i<=N; i++ )
{
if( N-i-P[i].k<0 ){ ok=false; break; }
int num=min( P[i].k, N-i-P[i].k );
update( num, i, 1, 1, N );
}
if(!ok){ printf("Case #%d: impossible\n", ++kase); continue; }
printf("Case #%d: ",++kase);
for( int i=1; i<=N; i++ )
{
int k=pos[i];
if(i==N)printf("%d\n",P[k].h);
else printf("%d ",P[k].h);
}
}
return 0;
}