Regional 2015 - Asia EC Final - C Suffixes and Palindromes

题目:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=719&page=show_problem&problem=5524

题意:
定义字符串 s[0..N1] 以第 i(0i<N) 个字符到末尾组成的子串称为后缀 i(suf[i]=s[i..N1]) ,将所有的 N 个后缀排序后,排名为i(0i<N)的是后缀 sa[i]
定义字符串 s[0..N1] 以第 i(0i<N) 个字符为中心的最长回文串长度为 pa[2i] ,以第 i 个字符和第(i+1)个字符之间为中心的最长回文串长度为 pa[2i+1]
你需要根据 sa[0..N1] pa[0..2N1] 来重建一个长度为 N 的字符串s,字符串只能包含小写字母,如果无解则输出Wrong calculation!,如果有解或多解则输出字典序最小解。
2N105,sa[0..N1]isapermutation,0pa[i]N

题解:
首先,只考虑 sa[0..N1] 的话,可以得到 s[sa[i1]]s[sa[i]] ,这样得到了一个偏序关系
其次,考虑什么时候必须有 s[sa[i1]]<s[sa[i]] ,这必然是 suf[sa[i1]+1]>suf[sa[i]+1] ,令 rk[i] 表示 suf[i] 在后缀中的排名,则上述条件即为 rk[sa[i1]+1]>rk[sa[i]+1] ,根据这个大于关系可以建一条有向边。
再者,考虑 pa[0..2N1] ,与manacher算法得到的信息相同,可以利用manacher算法所涉及到的 O(N) 个等于或不等关系来加强限制,减少冗余计算。
模拟manacher算法过程,若遇到字符之间的相等关系,比如是 s[L]=s[R] ,结合 sa[rk[L]] sa[rk[R]] 的关系,可以知道 sa[i](rk[L]irk[R]) 都是相等的,这个可以利用并查集将区间缩成点。
manacher匹配过程结束时,遇到的必须是字符之间的不等关系,如果不是则无解,如果是两个 s 中的字符有不等关系,比如s[L]s[R],结合 sa[rk[L]] sa[rk[R]] 的关系,得到一个大于关系并建一条有向边。
按照 i 升序枚举s[sa[i]]的取值,根据之前的有向边和 s[sa[i1]]s[sa[i]] 的关系,来计算最小的 s[sa[i]] ,如果存在区间缩成点后有自相矛盾的大于关系或者是小写字母不够用则无解,否则很容易证明得到的解 s[0..N1] 是字典序最小的,总时间复杂度 O(N)
细节较多,数据还是挺强的,笔者调bug调了一晚上……
以上都是偷懒的产物,本题只需将等于关系看成两个大于等于关系,直接建图跑差分约束即可。

代码:

#include <stdio.h>
#include <cstring>
#include <algorithm>
const int maxn = 100010, maxs = 26;
int t, n, sa[maxn], rk[maxn], ma[maxn << 1], tot, lnk[maxn], fa[maxn];
char str[maxn];
struct Edge
{
    int nxt, v;
} e[maxn << 2];
inline void scan(int &x)
{
    register int ch;
    while((ch = getchar()) < '0' || ch > '9');
    for(x = ch - '0'; (ch = getchar()) >= '0' && ch <= '9'; x = (x << 3) + (x << 1) + (ch - '0'));
}
int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main()
{
    scan(t);
    for(int Case = 1; Case <= t; ++Case)
    {
        scan(n);
        tot = 0;
        memset(lnk, -1, n * sizeof(int));
        for(int i = 0; i < n; ++i)
        {
            scan(sa[i]);
            rk[sa[i]] = i;
            fa[i] = i;
        }
        rk[n] = -1;
        for(int i = 1; i < n; ++i)
            if(rk[sa[i - 1] + 1] > rk[sa[i] + 1])
            {
                e[tot] = (Edge){lnk[i], i - 1};
                lnk[i] = tot++;
            }
        ma[0] = -1;
        ma[1] = ma[n << 1 | 1] = 0;
        for(int i = 2; i <= n << 1; ++i)
            scan(ma[i]);
        bool flag = 1;
        for(int i = 2, mx = 1, id = 1; i <= n << 1 && flag; ++i)
        {
            int cur = mx > i ? std::min(mx - i, ma[(id << 1) - i] + 1) : 0, pL, pR;
            flag &= cur <= ma[i] + 1 && i - ma[i] > 0 && i + ma[i] < n + 1 << 1;
            for(pL = i - cur, pR = i + cur; cur <= ma[i] && flag; ++cur, --pL, ++pR)
                if((~pL & 1) && pL < pR)
                {
                    int u = rk[(pL >> 1) - 1], v = rk[(pR >> 1) - 1];
                    if(u > v)
                        std::swap(u, v);
                    for(u = find(u), v = find(v); u < v && flag; v = find(v))
                    {
                        for(int it = lnk[v]; it != -1 && flag; it = e[it].nxt)
                            flag &= e[it].v < u;
                        fa[v] = find(v - 1);
                    }
                }
            if((flag &= (~pL & 1)) && pL >= 2 && pR <= n << 1)
            {
                pL = rk[(pL >> 1) - 1];
                pR = rk[(pR >> 1) - 1];
                if(pL > pR)
                    std::swap(pL, pR);
                e[tot] = (Edge){lnk[pR], pL};
                lnk[pR] = tot++;
            }
            if(mx < i + cur)
            {
                mx = i + cur;
                id = i;
            }
        }
        char last = 'a';
        for(int i = 0, j = 0; i < n && flag; i = j)
        {
            char cur = last;
            for(++j; j < n && find(i) == find(j); ++j);
            for(int k = i; k < j && flag; ++k)
                for(int it = lnk[k]; it != -1 && flag; it = e[it].nxt)
                    if(find(e[it].v) == find(i))
                        flag = 0;
                    else if(cur <= str[sa[e[it].v]])
                        cur = str[sa[e[it].v]] + 1;
            if(cur > 'z')
                flag = 0;
            else
            {
                for( ; i < j; ++i)
                    str[sa[i]] = cur;
                last = cur;
            }
        }
        str[n] = '\0';
        if(!flag)
            printf("Case #%d: Wrong calculation!\n", Case);
        else
            printf("Case #%d: %s\n", Case, str);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值