嘘~ 正在从服务器偷取页面 . . .

洛谷P2870 [USACO07DEC] Best Cow Line G 题解


洛谷P2870 [USACO07DEC] Best Cow Line G 题解

题目链接:P2870 [USACO07DEC] Best Cow Line G

题意

Farmer John 打算带领 \(N\)\(1 \leq N \leq 5 \times 10^5\))头奶牛参加一年一度的”全美农场主大奖赛“。在这场比赛中,每个参赛者必须让他的奶牛排成一列,然后带领这些奶牛从裁判面前依此走过。

今年,竞赛委员会在接受报名时,采用了一种新的登记规则:取每头奶牛名字的首字母,按照它们在队伍中的次序排成一列。将所有队伍的名字按字典序升序排序,从而得到出场顺序。

FJ 由于事务繁忙,他希望能够尽早出场。因此他决定重排队列。

他的调整方式是这样的:每次,他从原队列的首端或尾端牵出一头奶牛,将她安排到新队列尾部。重复这一操作直到所有奶牛都插入新队列为止。

现在请你帮 FJ 算出按照上面这种方法能排出的字典序最小的队列。

输入格式

第一行一个整数 \(N\)

接下来 \(N\) 行每行一个大写字母,表示初始队列。

输出格式

输出一个长度为 \(N\) 的字符串,表示可能的最小字典序队列。

每输出 \(80\) 个字母需要一个换行。

考虑贪心。如果两端不相同,就取较小的那个。

如果两端相同,就比较里面那个,比如 \(\tt ACSBBA\) 就去后面那个。

如果里面那个也相同呢?比如 \(\tt CAEAAC\) ,如果还按照刚才的取就会得到 \(\tt CACAAE\) ,而正解是 \(\tt{CAACAE}\)

因此我们需要一直比较直到两个不相同,我们可以二分这个长度,然后用字符串哈希比较两个串。

时间复杂度 \(\mathcal{O}(n \log n)\) ,代码不贴了。

下面再来讲讲后缀数组(SA)的做法。设 \(f_i\) 为第 \(i\) 个后缀,\(g_i\) 为第 \(i\) 个前缀。

因为取两端字符的时候通常是用两个指针 \(l,r\) 维护,所以下面定义 \(s_l,s_r\) 为当前的两端。

如果 \(s_l=s_r\) ,我们直接比较 \(f_l\)\(g_{r}\) ,取较小的那一端(其实就是上面的那个贪心)

按照套路,我们可以构造一个原串 + 0 + 反串的串,然后跑后缀排序。

比如 \(\tt{AABCAA}\) 可以构造 \(\mathtt{AABCAA}\,0\,\mathtt{AACBAA}\)​ (中间是零)。不过本题其实不需要这么构造

时间复杂度 \(\mathcal{O}(n \log n)\) ,肯定比字符串哈希慢点。

代码:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(1e6 + 15))
#define check(i, w) (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + w] == tmp[sa[i - 1] + w])

char s[N], ans[N];
int sa[N], rk[N * 2], tmp[N * 2], cnt[N];
void sort(const int n, const int m, int w)
{
    memset(cnt, 0, sizeof(int) * (m + 5));
    for(int i = 1; i <= n; i++) tmp[i] = sa[i];
    for(int i = 1; i <= n; i++) ++cnt[rk[tmp[i] + w]];
    for(int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
    for(int i = n; i; i--) sa[cnt[rk[tmp[i] + w]]--] = tmp[i];
}
void init(const int n)
{
    const int m = max(n, 333);
    for(int i = 1; i <= n; i++) { sa[i] = i, rk[i] = s[i]; }
    for(int w = 1; w < n; w *= 2)
    {
        sort(n, m, w); sort(n, m, 0);
        for(int i = 1; i <= n; i++) tmp[i] = rk[i];
        for(int i = 1, p = 0; i <= n; i++)
            if(check(i, w)) rk[sa[i]] = p; else rk[sa[i]] = ++p;
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n; cin >> n;
    for(int i = 1; i <= n; i++) cin >> s[i];
    for(int i = 1; i <= n; i++) s[2 * n + 1 - i + 1] = s[i];
    init(2 * n + 1); int l = 1, r = n, t = 0;
    while(l < r)
    {
        if(s[l] < s[r]) ans[++t] = s[l++];
        else if(s[l] > s[r]) ans[++t] = s[r--];
        else {
            if(rk[l] < rk[2 * n + 1 - r + 1]) ans[++t] = s[l++];
            else ans[++t] = s[r--];
        }
    }
    ans[++t] = s[l];
    for(int i = 1; i <= n; i++) { cout << ans[i]; if(i % 80 == 0) cout << '\n'; }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/or4vdm00

[2] https://www.luogu.com.cn/article/mv2w9gwz

[3] https://www.luogu.com.cn/discuss/645628


题外话

这道题感觉以前做过,不过当时连字符串哈希都没搞懂。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录