原题链接
HDU:点我QωQ
题意简述
一群人排队抢 去银行,在银行里面排了一个队。每一个人不记得自己具体在哪个位置,但是记得前面或后面有多少人比
t
a
ta
ta高,以及自己的身高。给出每个人的描述(格式见输入),输出珂能的排列中字典序最小的那一个。
数据
输入
多组数据。第一行一个 T T T表示多少数据。接下来每个数据中有一个 n n n,表示人数。接下来 n n n行,每行两个正整数, h i h_i hi和 k i k_i ki, h i h_i hi表示第 i i i个人的身高, k i k_i ki表示第 i i i个人记得前面/后面有 k i k_i ki个人比 t a ta ta高。
输出
对于第 i i i组数据,输出" C a s e    # i :    S Case\; \#i:\; S Case#i:S", i i i是第几个数据, S S S是字典序最小的满足条件的排列。没有输出impossible。
样例
输入
3
3
10 1
20 1
30 0
3
10 0
20 1
30 0
3
10 0
20 0
30 1
输出
Case #1: 20 10 30
Case #2: 10 20 30
Case #3: impossible
(比较毒瘤,提供复制功能:)
Case #1: 20 10 30
Case #2: 10 20 30
Case #3: impossible
思路
我们想一个暴力的思路:把每个人按身高升序排序,先考虑身高矮的。设一个数组表示答案,找最靠前的一个点(
O
(
n
)
O(n)
O(n)暴力找)满足前面/后面有
k
i
k_i
ki个空位(有空位说明后面有元素过来,因为是排好序的,所以过来的元素的
h
h
h肯定高于
h
i
h_i
hi),放在这个位置上。举个栗子(第一组样例):
先是这样的(留三个空):
∗
,
∗
,
∗
\begin{matrix} *,*,* \end{matrix}
∗,∗,∗
(输入数据已经拍好序了)
考虑第一个。显然,在第
2
2
2个空格是唯一满足条件的(左边有
1
1
1个,右边也是
1
1
1个)。我们把它放在第
2
2
2个位置:
∗
,
10
,
∗
\begin{matrix} *,10,* \end{matrix}
∗,10,∗
然后是
20
20
20.第一个位置的右边有
1
1
1个,第三个位置的左边有
1
1
1个,均满足条件。放在
1
1
1位置,因为靠前。变为:
20
,
10
,
∗
\begin{matrix} 20,10,* \end{matrix}
20,10,∗
最后
30
30
30只有一个位置了。发现剩下这一个位置满足条件了,放在这里。
20
,
10
,
30
\begin{matrix} 20,10,30 \end{matrix}
20,10,30
输出
20
,
10
,
30
20,10,30
20,10,30。
但是这个找位置是 O ( n ) O(n) O(n)的,每个都要找一遍,就 O ( n 2 ) O(n^2) O(n2)了。我们考虑第 i i i个人,显然有两种情况:
- 前面有 k k k个位置
- 后面有 k k k个位置
设现在还剩 c n t cnt cnt个位置(明显 c n t = n − i cnt=n-i cnt=n−i),那么后面有 k k k个位置相当于前面有 c n t − k cnt-k cnt−k个位置。如果这两个有一个是负的,那就无解了。。。如果没有负的,说明还有解。此时我们要把 h i h_i hi插入到前面 m i n ( k i , c n t − k i ) min(k_i,cnt-k_i) min(ki,cnt−ki)个 0 0 0的位置上。令 m i n ( k i , c n t − k i ) = p min(k_i,cnt-k_i)=p min(ki,cnt−ki)=p。
如何优化呢?我说能用线段树,我自己都不信。。。但真的珂以。说实话,我第一次见线段树能这么用。首先我们要维护好区间剩下的空位个数。对于一个 p p p,如果
当前区间长度 > 1 >1 >1。此时,如果
左半空位个数 > = p >=p >=p,那么说明这个位置在左半段
否则在右半段。此时 p p p要减去左半段的空位个数,然后在右半段查找。至于为什么要减去左半段空位的个数。。。因为整个右半段都在左半段的右边,设左半段有 l S lS lS个空位,那么一个空位在右半段是第 k k k个,在整个区间内就要加上左半段那几个,也就会变成 k + l S k+lS k+lS个。所以到右半段查找要 − l S -lS −lS。
否则(即当前区间长度 = 1 =1 =1)。显然此时这个区间所表示的那个点就是答案了。我们设一个 a n s ans ans数组,把 h i h_i hi放在这里(即 a n s [ L ] ans[L] ans[L], L L L表示当前区间左端点。当然写 R R R也珂以,因为此时区间长度是 1 1 1,满足 L = = R L==R L==R。)当然,还要把这里空位的个数减 1 1 1。
就是这么厉害。代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
//Flandle_Scarlet is my wife
{
#define N 100100
int ans[N];
class SegmentTree
{
private:
struct node
{
int l,r;
int s;
}tree[N<<2];
public:
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define S tree[index].s//维护区间剩下多少空位
#define lL tree[ls].l
#define lR tree[ls].r
#define lS tree[ls].s
#define rL tree[rs].l
#define rR tree[rs].r
#define rS tree[rs].s
void Update(int index)
{
S=lS+rS;
}
void Build(int l,int r,int index)
{
L=l,R=r;
if (l==r)
{
S=1;//注意了,初始每个长度为1的区间剩下的空位的个数是 1 !!!!!!!!!!
//!!!!!!!!!! 很重要
return;
}
int mid=(l+r)>>1;
Build(l,mid,ls);
Build(mid+1,r,rs);
Update(index);
}
void Change(int p,int h,int index)
{
if (L==R)//如果L==R,说明找到了答案
{
ans[L]=h;//记录答案
S=0;//减少空位个数
return;
}
if (p<=lS) Change(p,h,ls);
else Change(p-lS,h,rs);//记得要减lS !!!!!!!!!!
Update(index);
}
}T;
struct node
{
int h,k;
}a[N];bool operator<(node x,node y){return x.h<y.h;}
int n;
void Input()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&a[i].h,&a[i].k);
}
sort(a+1,a+n+1);
}
void Solve()
{
T.Build(1,n,1);
for(int i=1;i<=n;++i)
{
int cnt=n-i;
int t=min(a[i].k,cnt-a[i].k);
if (t<0)
{
printf(" impossible\n");
return;//即使判无解
}
T.Change(t+1,a[i].h,1);
}
for(int i=1;i<=n;++i)
{
printf(" %d",ans[i]);
}putchar('\n');//输出答案
}
void Main()
{
if (0)
{
freopen("in.txt","r",stdin);
freopen("ans.txt","w",stdout);
}
int T;scanf("%d",&T);
for(int i=1;i<=T;++i)
{
Input();
printf("Case #%d:",i);
Solve();
}
}
};
int main()
{
Flandle_Scarlet::Main();
return 0;
}