POJ 3581 Sequence(后缀数组)

Time Limit: 5000 MS Memory Limit: 0 KB
64-bit integer IO format: %I64d , %I64u Java class name: Main

Description
Given a sequence, {A1, A2, …, An} which is guaranteed A1 > A2, …, An, you are to cut it into three sub-sequences and reverse them separately to form a new one which is the smallest possible sequence in alphabet order.
The alphabet order is defined as follows: for two sequence {A1, A2, …, An} and {B1, B2, …, Bn}, we say {A1, A2, …, An} is smaller than {B1, B2, …, Bn} if and only if there exists such i ( 1 ≤ i ≤ n) so that we have Ai < Bi and Aj = Bj for each j < i.

Input
The first line contains n. (n ≤ 200000)
The following n lines contain the sequence.

Output
output n lines which is the smallest possible sequence obtained.

Sample Input
5
10
1
2
3
4

Sample Output
1
10
2
4
3

Hint
{10, 1, 2, 3, 4} -> {10, 1 | 2 | 3, 4} -> {1, 10, 2, 4, 3}

Source
POJ Founder Monthly Contest – 2008.04.13, Yao Jinyu

题意:
将序列分成3段,每一段都不为空,然后分别翻转之后得到一个字典序最小的序列,输入加while就WA了

第一段:
该题要分成3段且每一段都不为空,第一段反过来的字典序必定要最小这样才能使开头最小,我们可以先将整个数组翻转过来,然后除开最后2个(最后至少要剩下两个组成最后两段,每段1个)之外利用后缀数组寻找字典序最小的然后输出即可。由于有前置条件A1 > A2, …, An的限制,第一段的分离可以单独考虑,也就是说不会出现如下不适用这样分段解法的情况:
样例(我们只讨论第一段,这个反例建议理解思路和代码后推导):
6
1 1 2 1 3 3
假如我们第一段利用后缀数组找最小的字典序,除开最后两个3 3,剩下1 1 2 1中字典序最小的是1,然后在剩余的1 2 1 3 3中得到最小的便是1 2 1 3 3,组合起来就输1 1 2 1 3 3很明显这个分段可以分成1 1 1 2 3 3({1 1 | 2 1 | 3 3})。

剩余两段:
这两段不能单独考虑了,就想上述“不适用”例子一样,这里没保证剩余的段有什么前提条件,因此必须两端同时考虑。借助《挑战程序设计学竞赛》一书的思想,假设我们剩余两段最终分解为1|2,那么剩余两段的在原序列的顺序为1->|2->,翻转之后为<-1|<-2。
此处解决的方法为:在分离第一段之前时候我们先翻转整个序列(原序列:第一段|1->|2->),然后利用后缀数组找到第一段并输出,前面剩余的序列便为<-2|<-1(这里第一段已输出然后可以不考虑了)。我们将其复制一遍得到<-2|<-1|<-2|<-1,现在我们可以在这个数组中利用后缀数组寻找长度为除开第一段之后剩余长度的最小字典序的序列(好拗口)。输出的时候一定要找到的序列开始范围在[1, n)之间的,这样能保证两段都不为空。因为我们假设最终分解为1|2,所以直接输出的就是<-1|<-2,也就是1->|2->的翻转。
这里写图片描述

Accepted:

#include <cstdio>
#include <algorithm>
using namespace std;
const int Maxn = 200005;

int n, k;
int Rank[2*Maxn], tmp[2*Maxn], sa[2*Maxn], a[2*Maxn];

void reverseArray(int l, int r)
{
    while(l<r)
    {
        int temp = a[l];
        a[l++] = a[r];
        a[r--] = temp;
    }
}

bool compareSa(const int i, const int j)
{
    if(Rank[i]!=Rank[j])
        return Rank[i]<Rank[j];
    int ri = (i+k>n)?-1:Rank[i+k];
    int rj = (j+k>n)?-1:Rank[j+k];
    return ri<rj;
}

void getSa()
{
    for(int i = 0; i < n; ++i)
    {
        sa[i] = i;
        Rank[i] = a[i];
    }

    for(k = 1; k <= n; k*=2)
    {
        sort(sa, sa+n, compareSa);

        tmp[sa[0]] = 0;
        for(int i = 1; i < n; ++i)
            tmp[sa[i]] = tmp[sa[i-1]] + (compareSa(sa[i-1], sa[i])?1:0);

        for(int i = 0; i < n; ++i)
            Rank[i] = tmp[i];
    }
}

int main()
{
    scanf("%d",&n); // 输入加while就WA了
    for(int i = 0; i < n; ++i)
        scanf("%d", &a[i]);

    reverseArray(0, n-1);
    getSa();

    for(int i = 0; i <= n; ++i)
        if(sa[i]>1)
        {
            for(int j = sa[i]; j < n; ++j)
                printf("%d\n", a[j]);
            n = sa[i];
            break;
        }

    for(int i = 0; i < n; ++i) //Copy
        a[i+n] = a[i];

    n<<=1;
    getSa();

    for(int i = 0; i <= n; ++i)
        if(sa[i]>0 && sa[i]<n/2)
        {
            int first = sa[i], last = sa[i]+n/2;
            while(first<last)
                printf("%d\n", a[first++]);
            break;
        }

    return 0;
}

这里总结一下我的错误:
WA/RE/TLE等,我只贴出一个代码供参考:

#include <cstdio>
#include <algorithm>
using namespace std;
const int Maxn = 200005;

int n, k;
int Rank[2*Maxn], tmp[2*Maxn], sa[2*Maxn];

void reverseArray(int *a, int l, int r)
{
    while(l<r)
    {
        int temp = a[l];
        a[l++] = a[r];
        a[r--] = temp;
    }
}

bool compareSa(const int i, const int j)
{
    if(Rank[i]!=Rank[j])
        return Rank[i]<Rank[j];
    int ri = (i+k>n)?-1:Rank[i+k];
    int rj = (j+k>n)?-1:Rank[j+k];
    return ri<rj;
}

void getSa(int *a) //这个函数的排序可以省掉空字符串,也就是n可以不用考虑,对照AC代码
{
    for(int i = 0; i <= n; ++i)
    {
        sa[i] = i;
        Rank[i] = (i==n)?-1:a[i];
    }

    for(k = 1; k <= n; k*=2) //此处一定要遍历到k<=n 不能是k<=n/2,可以拿n是奇数的时候来模拟一遍
    {
        sort(sa, sa+n+1, compareSa);

        tmp[sa[0]] = 0;
        for(int i = 1; i <= n; ++i)
            tmp[sa[i]] = tmp[sa[i-1]] + (compareSa(sa[i-1], sa[i])?1:0);

        for(int i = 0; i <= n; ++i)
            Rank[i] = tmp[i];
    }
}

void stringCopy(int* a, int* b, const int num)
{
    for(int i = 0; i < num; ++i)
        a[i] = b[i];
}

int a[Maxn]; //此处数组不够大,复制的时候有可能是2*Maxn

int main()
{
    scanf("%d",&n);
    for(int i = 0; i < n; ++i)
        scanf("%d", &a[i]);

    reverseArray(a, 0, n-1);
    getSa(a);

//    for(int i = 1; i <= n; ++i)
//        if(sa[i]>1)
//    我个人认为在把“空字符串”考虑进最小字典中的时候
//    实际上肯定有sa[0]==n(反正我没举出反例),那么从1开始就行了,但是提交的时候就是TLE
//    改成
//    for(int i = 0; i <= n; ++i)
//        if(sa[i]>1 && sa[i]<n)
//    就过了
    for(int i = 1; i <= n; ++i) 
        if(sa[i]>1)
        {
            for(int j = sa[i]; j < n; ++j)
                printf("%d\n", a[j]);
            n = sa[i];
            break;
        }

    stringCopy(a+n, a, n);
    n<<=1;
    getSa(a);

    for(int i = 1; i <= n; ++i)
        if(sa[i]>0 && sa[i]<n/2)
        {
            for(int j = 0; j < n/2; ++j)
                printf("%d\n", a[sa[i]+j]);
            break;
        }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ 2182是一道使用树状数组解决的题目,题目要求对给定的n个数进行排序,并且输出每个数在排序后的相对位置。树状数组是一种用来高效处理前缀和问题的数据结构。 根据引用中的描述,我们可以通过遍历数组a,对于每个元素a[i],可以使用二分查找找到a到a[i-1]中小于a[i]的数的个数。这个个数就是它在排序后的相对位置。 代码中的query函数用来求前缀和,add函数用来更新树状数组。在主函数中,我们从后往前遍历数组a,通过二分查找找到每个元素在排序后的相对位置,并将结果存入ans数组中。 最后,我们按顺序输出ans数组的元素即可得到排序后的相对位置。 参考代码如下: ```C++ #include <iostream> #include <cstdio> using namespace std; int n, a += y; } } int main() { scanf("%d", &n); f = 1; for (int i = 2; i <= n; i++) { scanf("%d", &a[i]); f[i = i & -i; } for (int i = n; i >= 1; i--) { int l = 1, r = n; while (l <= r) { int mid = (l + r) / 2; int k = query(mid - 1); if (a[i > k) { l = mid + 1; } else if (a[i < k) { r = mid - 1; } else { while (b[mid]) { mid++; } ans[i = mid; b[mid = true; add(mid, -1); break; } } } for (int i = 1; i <= n; i++) { printf("%d\n", ans[i]); } return 0; } ``` 这段代码使用了树状数组来完成题目要求的排序功能,其中query函数用来求前缀和,add函数用来更新树状数组。在主函数中,我们从后往前遍历数组a,通过二分查找找到每个元素在排序后的相对位置,并将结果存入ans数组中。最后,我们按顺序输出ans数组的元素即可得到排序后的相对位置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [poj2182Lost Cows——树状数组快速查找](https://blog.csdn.net/aodan5477/article/details/102045839)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [poj_2182 线段树/树状数组](https://blog.csdn.net/weixin_34138139/article/details/86389799)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值