方式1 单调栈 + 规律 / 方式2 使用树状数组对方式1进行优化,使得其健壮性更强(适用于更多的情况)/ 方式3 单调栈 + 拓扑图:Stack

题目链接:https://ac.nowcoder.com/acm/contest/11253/K

题目:

在单调栈的运算过程中,当栈顶大于a[i]时,栈顶不断退栈。直到栈定值小于等于a[i]或者栈顶为空时, 在将a[i]存入到栈中。 并将当前栈中的值的个数存入到b[i]中(a[i]下标 i 与b[i]下标i相同)。

现在有一组关于n的排列(1~n的组合)组成a[],但我们不知道,但是会给出k个b[]的值。问能否通过这k个b[]的值算出a[]的排列(答案可以有多个,其中一个满足条件的即可。)。如果没有这样的a[]的排列的话输出-1。

输入:

第一行输入 n ,k;

后面k行每行输入两个值,输入b[idx] 的idx , 以及b[idx]对应的值。

输出:

如果能够有这么一种排列,输出a[]的排列。

如果不能够有这么一种排列,则输出-1.

方式1:单调栈 + 拓扑图

思路:

首先我们根据给出的b[],来进行单调栈的处理,如果b[i]没有被赋值,则可以将i插入。因为我们只要当遍历到b[j]这个有输入时,单调栈中的元素个数t >= b[j ] - 1,就可以满足构造出a[],所以b[i]未输入值插入i入栈中,是成立的。然后判断栈中元素t 是否 >= b[j] - 1,如果不成立,则输出-1. 否则的话,根据栈中元素t和 b[j] 的关系,去将栈中元素退栈,直到t == b[j] - 1.  而退栈的那些元素的含义就是a[退栈元素]  > a[j]. 就连一条  退栈元素值 -> j的一条边,(也就是构造一个大值 -> 小值的边),最后当无法退栈后, 说明a[栈顶元素] <= a[j]. 然后连一条j ->栈顶元素的边。(构造一个大值 - >小值的边)。

最后得到一个拓扑图。因为我们一直通过大值 -> 小值, 不可能成环,有向无环图一定是拓扑图,然后求一遍拓扑序。 而这个拓扑序,用n,n-1,n-2不断的赋值(因为入度为0,即为当前最大值)。即可a[].

思路分析:

由于我们只知道部分b[],所以通过单调栈的方式将对应的各个a[]之间的关系表示出来。

首先需要知道,单调栈中维护的是什么。 此时我们单调栈中存入的下标,维护着单调栈中的元素个数 与 b[]之间的关系。

情况1:如果此刻的下标i,对应的b[i]并未给出,那么实际上我们可以将对应的i加入到单调栈中。因为我们只要维护当到达存在输入值的b[j]的时候,单调栈中的元素 + 1 >= b[j]即可。因为满足这个条件则必定能够组成一个成立的a[]。

所以栈中元素多了,没有关系。一样能够构造出对应的a[]. 但是如果栈中元素少了,则就无法构造出对应的a[]. 于是我们将下标i加入到单调栈中。(而这个加入到单调栈中实际上是有含义的,含义就是a[栈顶]要大于其他a[栈内元素]的含义)。

为什么当单调栈中元素  + 1 < b[j]时不满足呢? 因为b[j] 的最大值为 b[j - 1] + 1. 所以b[j]不会比当前单调栈中元素多1以上。这是无法实现的。

情况2:当前下标i对应的b[i]输入了值, 如果b[i] 大于 栈中元素个数 + 1则无法构造出a[].

情况3:当前下标i对应的b[i]输入了值,如果b[i]小于 栈中元素个数 + 1,则将栈顶元素不断退栈。(相当于存入的a[栈顶元素] > a[i]),直到b[i] == 栈中元素个数 + 1.

那么知道了对单调栈的操作和意义,如何建图呢?

我们所建的图的每一条边的含义都是 大值 指向 小值。( 大值 -> 小值)。

在上面的情况3中,栈顶不断退栈,所以对应的a[栈顶值] 大于 a[i]. 所以将 栈顶值 与 i之间连一条边,表示a[栈顶值](大值) -> a[i].

最后栈中留下的值,与a[i]的关系就是,a[栈顶值] 小于 a[i].  所以a[i] - > a[栈顶值]。

按照这两种情况进行建图。

是否一定要对此两种情况都进行建边表示关系呢?是否可以只对一种情况进行建边关系呢?

实际上是不行的。必须对两种情况都进行建图。

举例:

最小值a,次大值b,最大值c, 次小值d。

按照顺序,依次进栈

最小值a,次大值b,最大值c,

并按照此种关系进行建图, 

c ->b , b -> a.

然后次小值进入, 

栈中变为,a,d

c->d,b->d,d->a

方式1 单调栈 + 规律 / 方式2 使用树状数组对方式1进行优化,使得其健壮性更强(适用于更多的情况)/ 方式3 单调栈 + 拓扑图:Stack_第1张图片

形成了一个拓扑图。正确。

那么如果只对一种情况建图,如退栈时才建图的话呢?

b,c,退栈,则b -> d, c->d. 

 方式1 单调栈 + 规律 / 方式2 使用树状数组对方式1进行优化,使得其健壮性更强(适用于更多的情况)/ 方式3 单调栈 + 拓扑图:Stack_第2张图片

可以很明显的看出图是不完整的。

如果只对剩余栈中未退栈元素 和 当前a[i] 进行建图的话呢?

举例:

最大a,次大b,小c,最小d

a进栈,

a退栈,b进栈

b退栈,c进栈

c退栈,d进栈

则可以发现,无边可以建立。

所以,两种情况都需要进行建图创边。最后得到一个拓扑图。

为什么一定是拓扑图呢?因为其为有向图。 并且一定无环。

边的含义就是大值 指向 小值。

如果有环的话, 大值1 -> 小值1 , 小值1 -> 大值1. 就矛盾了。

所以按照此种建图,必定不会有环,又是有向图。 所以必定为拓扑图。

那么拓扑图,就必定有入度为0. 而入度为0,就是最大值,赋值为n.

然后通过这个点不断进行求解拓扑序列的过程中,不断赋值,n - 1, n -2 , n -3 ......1。

还需要注意一点: 

方式1 单调栈 + 规律 / 方式2 使用树状数组对方式1进行优化,使得其健壮性更强(适用于更多的情况)/ 方式3 单调栈 + 拓扑图:Stack_第3张图片

所以建边的时候要注意需要多建些边。

代码实现:

# include 
# include 
# include 
using namespace std;

const int N = 1e6 + 10 , M = 2 * N;

int st[N],tt = -1; // 单调栈
int a[N];
int b[N];

int n,k;

int h[N],e[M],ne[M],ru[N],idx; // 同时需要注意一点,建图的时候边的个数可能会大于N。
queue q;

//所连的边为 大值 指向 小值

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void top()
{
    int temp = n;
    for(int i = 1 ; i <= n ; i++)
    {
        if(ru[i] == 0)
        {
            q.push(i);
        }
    }
    while(q.size())
    {
        int t = q.front();
        q.pop();
        a[t] = temp--;
        for(int i = h[t] ; i != -1; i = ne[i])
        {
            int j = e[i];
            ru[j]--;
            if(ru[j] == 0)
            {
                q.push(j);
            }
        }
    }
}

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d %d",&n,&k);
    while(k--)
    {
        int x;
        scanf("%d",&x);
        scanf("%d",&b[x]);
    }

    int res = 0; // 当前栈中的元素个数

    for(int i = 1 ; i <= n ; i++)
    {
        if(b[i])
        {
            if(b[i] > res + 1) // b[i]不可能大于当前栈中元素个数 + 1
            {
                printf("-1\n");
                return 0;
            }
            while(res != 0 && b[i] < res + 1)  //如果当前栈中元素个数多于b[i]所要的,则说明栈中有部分元素应该退栈,即对应值比a[i]大
            {
                // 栈中值大于 a[i].
                add(st[tt],i); // 栈中值指向a[i]
                ru[i]++;  // 入读增加
                //printf("----%d---->%d %d---\n",st[tt],i,ru[i]);
                tt--;
                res--;
            }  //直到b[i] == res + 1.
        }
        
        
        if(tt != -1)
        {
            add(i,st[tt]); //栈中的值小于a[i]
            ru[ st[tt] ]++;  // 入度增加
           // printf("----%d---->%d %d---\n",i,st[tt],ru[st[tt]]);
        }
        
        
        st[++tt] = i; //将i存入到栈中,
        res++;  // 栈中元素增加了一个
    }

    top();

    for(int i = 1 ; i <= n ; i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

方式2:单调栈  + 规律

思路:

首先将b[]的值我们进行初始化赋值,因为b[i] = temp, 则b[1~i-1]必定至少出现一次1~temp的值,所以我们让这些值相邻,b[i- 1] = b[i] - 1, b[i -2] = b[i - 1] - 1, 同时注意,可能这时候,b[]会被赋为负值,但是最小也为1. 所以b[i - 1] = max(1,b[i] - 1), b[i - 2] = max(1,b[i - 1] - 1)以此类推。

同时b[]可能最右边没有赋值,那么给其b[]全部赋值为1即可。因为我们能够构造出a[]的原因就是b[i] - b[i - 1] <= 1,且b[1] 等于 1 即可。

最后将b[i]的值和下标i存入到pair数组中, 对其进行小值在前,小值相同则下标更大的在前的方式进行排序。

因为b[最右边某个下标] = 1, 则说明这个值必定为1。

因为1的存入,会将左边所有值都给清空,只留下1,同时右边无法清掉1。所以b[]最右边的1,一定存1. 

1存入后, 在存入2.则右边第二个1,或者右边第一个2存入的就是2这个值。

如: 1    1

或者1      2

或者2      1的情况

以此类推,按照排好的顺序,对应的下标a[i] = 1,2,3,4,5,6,7,这样按顺序赋值即可。

思路分析:

首先,先让我们想一下输出-1的情况是什么样的呢?

1. 如果b[idx]的值 > idx 的话,一定不满足条件。

因为在第idx个,b[idx]最多只能有idx个。

2.如果两个相邻的b[],如果b[i + 1] - b[i]大于1,则不可能。

因为当到下标i的时候,栈中最多只能由b[i]个,那么到i + 1下标的时候,就算栈中一个没有退,在加上自己a[i + 1]也最多为b[i] + 1个。 所以b[i + 1] - b[i] 不能 > 1.

但是b[i + 1]可以小于b[i]. 因为比如a[i + 1]为1,则前面的全部退栈,b[i + 1] = 1.  

 分析2:

同时我们还应该要知道,如果给了一个b[i] == temp的话,那么说明,下标i的前面, b[1 ~ i - 1](下标从1开始)一定存在 1 ~ temp - 1所有至少出现一次。因为 temp的存在,必定至少经过了一次从temp - 1通过 + 1得到temp.

当然,实际上也可能会是从 大于temp 通过退栈 到达栈中temp个。 但是如上面标红所说,至少要经过一次从temp - 1经过 + 1到达temp,  所以下标b[1 ~ i  - 1]中必定存在1 ~ temp - 1的所有值。

那么我们知道的为b[i],而题目又是只要满足条件的一个,所以不妨让b[i - 1] = b[i] - 1,  b[i - 2] = b[i - 1] - 1,以此类推。一直到b[j]存在 或者 b[1].

而这就有4种需要考虑到的情况了:

1.b[i - 1] = b[i] - 1的话,满足不输出 -1 的 一个条件,那就是b[i] - b[i - 1] 不 > 1。

2.那么另一个条件呢? 如果b[idx] > idx是否要判断呢?

实际上我们可以通过另一种方式进行判断,

因为存在前面b[j]通过了这种方式进行了初始化(因为存在多个已知的b[]),所以当我们通过此种方式到达了b[j + 1]后停下了, 那么判断b[j + 1] - b[j] 是否 > 1即可。

或者就是b[j]还未存在,现在初始化的是第一个b[],那么就到b[1] 是否等于1,因为b[1]必定为1,如果b[1]都不等于1,那么必定b[idx] > idx了。

3.情况3就是如果b[]的下标很大。

举例:比如现在已知,b[100] = 5;

则第b[99] =4 , b[98] = 3, b[97] = 2, b[96] = 1,

注意:这时b[95~1]呢?该怎么赋值?

难到赋值为负数吗?肯定不可能。

那么应该怎么赋值呢?

赋值为1就可以了(当然实际上可以赋很多值,只是1必定正确)。 

因为赋值为1,如果到了b[1] 必定为1,没得问题。

同时,b[j] - b[j - 1]必定小于1,因为b[j] == 1了,b[j - 1]不管是1,还是原先已经赋值,得到的结果必定小于1.

所以赋值为1即可,但是只要满足这两个条件的值,实际上都可以。只是1是最方便的,不用判断条件,

4.同时还要注意一个,我们通过b[i]然后赋值b[1~i-1],那么问题来了。

如果给出的b[idx]的idx最大的情况为10, 而我们给的n有100呢?

就会发现b[idx + 1 ~ 100]就还没有赋值,

而我们现在的思想是先赋值完所有的b[]。至于为什么先赋值完所有的b[],后面会说。

而这时候,和上面的情况3一样,我们把后面的值全部赋值为1即可。因为赋值为1的话,必定满足能够组成a[]的条件。当然其他的满足条件的值也可以。

比如b[idx + 1] = b[idx] + 1, b[idx + 2] = b[idx + 1] + 1.....

或者b[idx + 1] = b[idx] , b[idx + 2] = b[idx + 1]....都是满足条件的。

只是1最为方便,最简单,并且必定满足构成a[]的条件所以使用1而已。 

 那么最后一个问题:为什么要先构造好b[]

因为我们会发现这样一个方式。 首先我们先找到b[]的值为1的情况。

找到所有b[]为1的情况要干什么? 找到b[]中最右边的一个1,也就是下标最大的b[idx] = 1.

那么那个a[idx] 必定为1.

为什么?

证明:

如果在a[idx]的前面放的是1的话,由于我们a[]是1~n的排列.

所以可以知道,1放在idx的前面,那么1永远不会退栈,那么b[idx] 则至少为2不可能为1.矛盾了。

如果a[idx]的后面放的是1的话,比如a[idx + 10] 放的是1,那么b[idx + 10]必定为1,因为栈中所有值都退栈了,最后将a[idx + 10]放入,所以b[idx + 10]才为1,而我们找到了b[idx]为b[]中为1的最右边的情况, 那么就矛盾了。

所以可以知道,b[]最右边的1,那么a[]在这个位置必定为1。

同样的,b[]最右边的值为2,那么这个值必定为2,如果b[]最右边的1在它后面,则1被抢了,那么它的前面就不能放1,就放3. 如果b[]最右边的1在它前面,因为我们构造的时候就是这样,b[idx] = 2 , 则我们构造b[idx - 1] = b[idx] - 1, 也就是1了。 那么a[idx - 1]为1,a[idx] 为2.

满足条件。

以此类推。

所以我们先构造好所有的b[],然后根据构造好的b[],存入到pair []的数组中,对这个数组进行 值最小排序,如果值相同是,最右边的值放前面。

如1   1

则第一个1对应的值a[]赋值2, 第二个1对应的值a[]赋值为1. 

那么是否会存在 b[]中没有1的情况呢?是不可能存在的。

因为至少会存在b[1]为1,那么至少就会使得a[1] 为1, 也就是1一定会被用掉,然后去用2.

而按照我们创建b[]的方式可以知道,只要初始给值的时候,给出一个b[] = temp, 那么就至少会存在1~temp的值给b[],也就至少会用掉所有的1~temp.

以此类推, 比如给出的b[r(输入时给出的最大的下标)] = temp,

那么从b[r + 1] ... b[n]都为1, 则从右往左就用掉了1,2,3,..... n - r . 然后前面的根据b[]的大小,赋值n - r~r。 

 代码实现:

# include 
# include 
using namespace std;

const int N = 1e6 + 10;

int a[N];

int b[N];

pair p[N];

int n,k;

bool cmp(pair a , pair b)
{
    if(a.first == b.first)
    {
        return a.second > b.second;  // 如果值相同,则在右边的先赋值
    }
    return a.first < b.first; // 值小的在前面完成。
}

int main()
{
    scanf("%d %d",&n,&k);
    while(k--)
    {
        int idx , v;
        scanf("%d %d",&idx,&v);
        b[idx] = v;
    }
    for(int i = 1 ; i <= n ; i++)
    {
        if(b[i])
        {
            int j = i - 1;
            while(j)
            {
                if(b[j]) // 如果前面已经将b[j]赋值了,就可以跳出循环了
                {
                    break;
                }
                b[j] = max(1,b[j + 1] - 1); // 因为这样的话b[]可能为0或负数,但是b[]实际上最小为1
                j--;
            }
        }
    }
    for(int i = 1 ; i <= n ; i++)  // 因为我们是通过b[i]然后向左进行初始化的,可能存在部分后面的值没有进行初始化,而后面的初始化则只要后面的值 比 前面的值 不会大于1就可以了。比如全部赋值为1等等都是可以的。
    {
        if(!b[i])
        {
            b[i] = 1;
        }
    }
   
    // 而无法构建出的情况就是 b[j] - b[j - 1] > 1 就构建不出 或者 b[1]不等于1,也就是第一个b[i] 的值 > i
    if(b[1] != 1)
    {
        printf("-1\n");
        return 0;
    }
    for(int i = 2 ; i <= n ; i++)
    {
        if(b[i] - b[i - 1] > 1)
        {
            printf("-1\n");
            return 0;
        }
    }
    
    
    // 然后就是将b[]中的值存入到pair 中,因为我们会将值进行排序
    //排序方式:1.小值在前, 2.如果值相同,b[]的下标在右边的在前, 因为我们会先把1的值完成,最右边的1一定为1,
    for(int i = 1 ; i <= n ; i++)
    {
        p[i].first = b[i];
        p[i].second = i;
    }

    sort(p + 1, p + 1 + n , cmp);
    
    for(int i = 1 ; i <= n ; i++)
    {
        a[p[i].second] = i;
    }
    for(int i = 1 ; i <= n ; i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

但是还要注意一点,方式2解法的适用条件只适用于b[i - 1] - b[i] >= -1的情况下,才能够直接赋值:

方式2的解法只适用于,当b[i - 1] - b[i] >= -1的情况下才能够适用。

因为这道题目是单调栈的缘故,所以满足b[i - 1] - b[i] >= -1的情况,所以才能够使用这种,直接顺序赋值的情况。

有一个思路类似,但不能够直接赋值的题目:

https://www.acwing.com/problem/content/245/

此题为树状数组的一道题目,此题就不能够直接赋值。 虽然其思想也依然是最右边的最小值一定为1,但是其不能够顺序赋值,应为它不满足b[i - 1] - b[i] >= -1的条件。它的所有a[]不会退栈。而是找i前面小于a[i]所有值。

为什么必须要满足b[i - 1] - b[i] >= -1的条件才能用这种方式呢?

因为这样才能够满足前面进行的赋值一定大于后面进行的赋值,按照我们赋值的方式,b[]越大的,赋值越大, 而b[i - 1] 在b[i]的前面,比b[i]大,所以导致前面的赋值永远比后面的赋值要大。导致前面赋的值不会影响到后面的b[i].

而我们后面给出的这道树状数组的题目,则b[]的值跨度很大,出现前面b[i - 1] 要小于b[i]的情况,

所以这道题目,你会发现,所有赋值为b[i - 1] - b[i] >= -1下的赋值,用这个方法都是成立的。

但是给出b[i - 1]小于 b[i]的情况时,这个方法就无法成立,只能出现WA了。

比如b[23] = 0 , b[1] = 0 , b[22] = 1 , b[47] = 2;

那么a[23] = 1,a[1] = 2 , a[22] = 3,  a[47] 的构造是不可能出现b[47] == 2的情况的。

综上:方式2的解法,只适用于这种特殊的题目,单调栈下使得维护的b[]为b[i - 1] - b[i] >= -1的情况下才能用此解法(顺序赋值)。

方式3:

使用树状数组的方式,在方式2的基础上 来求解这道题目。(因为树状数组的方式 要比 方式2的顺序赋值更加的通用)

方式3的思路,基于题目“谜一样的牛”:

参考题目链接(谜一样的牛):https://www.acwing.com/problem/content/description/245/

参考题目题解:https://blog.csdn.net/qq_49120553/article/details/119004022

前面还是一样。算出所有的b[i].

举例来说:

b[]为: 1 2 3 2 1

那么我们从后往前进行计算, b[5] == 1, 所以可以知道,前面没有比它更小的值了,所以a[5] == 1.     (取值从原来的1,2,3,4,5 变为了  2,3,4,5)。

而b[4] == 2, 所以1~3里面有一个值比它小, 所以我们取剩下值中,倒数第二小的值,3.

所以a[4] == 3. (取值从原来的2,3,4,5变为了2,4,5)

而b[3] == 3 , 所以1~2里面有两个值比它小,所以我们取剩下的值中,倒数第三个小的值,5

所以a[3] == 5,(取值从原来的2,4,5,变为了 2,4)

而b[2] == 2, 所以1里面有一个值比它小,所以我们取剩下的中,倒数第二个小的,4

所以a[2] == 4.(取值从原来的2,4 变为了2)

所以a[1] == 2.

这样的方式求解。与方式2类似,但是又不尽相同,如上面所说,方式2的顺序赋值要求比现在的方式3要严格。需要b[i - 1] - b[i] >= -1才行。 

那么问题来了。如何实现方式3呢?

我们可以使用树状数组的方式来维护我们现在还能够用的数值。

由于树状数组维护的是前缀和。 所以我们树状数组中的前缀和的含义就是 1~x中还有多少个数。

每一个数在树状数组中刚开始的时候都是1,

每使用掉一个值,那么树状数组中这个值就被-1. 使得前缀和 -1.

举例:我们要找当前剩下的数中第 k 小的数。

那么就可以使用二分的方式进行查询。

寻找树状数组中第一个 1~x区间 其和 大于等于 k。(也就是1~x中还剩下大于等于k个值,而又是第一个大于等于k的,其实就是第一个和为k的情况, 那么x的含义不就是当前1~n中剩下的第k小的数了嘛。)

 所以可以通过树状数组 加 二分 的方式 实现。

备注:

思路三的由来完全是因为我这次刚好做到这个"谜一样的牛"这道题目,感觉与这道题目类似,于是使用了一下方式2求解那道题目,结果发现使用方式2的顺序赋值的方式无法求解出那道题目。 

但是通过自己举的一些思路,发现方式2解题方式,可以解掉那些b[i -1] - b[i] >= -1的情况,

 所以自行想了一下为什么b[i - 1] - b[i] >= -1才能使用方式2.  以及这两道题目的差别。

但未必正确。只是我个人感觉方式2的顺序赋值需要这个条件(b[i - 1] - b[i] >= -1),而方式3的树状数组的方式感觉更为通用而已 (对b[i]无条件限制)。

代码实现:

// 使用树状数组的方式求解

# include 
# include 
using namespace std;
 
const int N = 1e6 + 10;
 
int a[N];
 
int b[N];

int c[N];

int n,k;
 

int lowbit(int x)
{
    return x & -x;
}

void add(int x , int v)
{
    for(int i = x ; i <= n ; i += lowbit(i))
    {
        c[i] += v;
    }
}

int sum(int x)
{
    int res = 0;
    for(int i = x ; i > 0 ; i -= lowbit(i))
    {
        res += c[i];
    }
    return res;
}
 
int main()
{
    scanf("%d %d",&n,&k);
    while(k--)
    {
        int idx , v;
        scanf("%d %d",&idx,&v);
        b[idx] = v;
    }
    for(int i = 1 ; i <= n ; i++)
    {
        if(b[i])
        {
            int j = i - 1;
            while(j)
            {
                if(b[j]) // 如果前面已经将b[j]赋值了,就可以跳出循环了
                {
                    break;
                }
                b[j] = max(1,b[j + 1] - 1); // 因为这样的话b[]可能为0或负数,但是b[]实际上最小为1
                j--;
            }
        }
    }
    for(int i = 1 ; i <= n ; i++)  // 因为我们是通过b[i]然后向左进行初始化的,可能存在部分后面的值没有进行初始化,而后面的初始化则只要后面的值 比 前面的值 不会大于1就可以了。比如全部赋值为1等等都是可以的。
    {
        if(!b[i])
        {
            b[i] = 1;
        }
    }
   
    // 而无法构建出的情况就是 b[j] - b[j - 1] > 1 就构建不出 或者 b[1]不等于1,也就是第一个b[i] 的值 > i
    if(b[1] != 1)
    {
        printf("-1\n");
        return 0;
    }
    for(int i = 2 ; i <= n ; i++)
    {
        if(b[i] - b[i - 1] > 1)
        {
            printf("-1\n");
            return 0;
        }
    }
    
    
    for(int i = 1 ; i <= n ; i++)
    {
        add(i,1);  // 每个i值都可以用一次
    }
    
    for(int i = n ; i >= 1 ; i--)
    {
        int t = b[i]; // i的前面有h[i]个比它小的,所以它是当前还剩下值的第h[i] + 1小的值
        
        int l = 1 , r = n;
        
        while(l < r) // 找第一个大于等于t的值
        {
            int mid = (l + r) / 2;
            if(sum(mid) >= t)
            {
                r = mid;
            }
            else
            {
                l = mid + 1;
            }
        }
        a[i] = l;
        add(l,-1);  // l被用掉了。所以减1.
    }
   for(int i = 1 ; i <= n ; i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

你可能感兴趣的:(基础算法,栈,图论)