首页 > 其他分享 >【题解】CF700E Cool Slogans

【题解】CF700E Cool Slogans

时间:2023-01-05 11:45:59浏览次数:44  
标签:sz sam int 题解 len son fa Slogans CF700E

原题面很简洁,懒得概括了。

思路

后缀自动机 + 结论。

这种题出题人没有十年脑血栓都没法出。

结论 1:\(s_{i - 1}\) 必定是 \(s_i\) 的后缀。

考虑 \(s_i\) 中 \(s_{i - 1}\) 最后一次出现位置,记为 \(p\)。对于 \(s_i\) 中 \(p\) 之后的位置,把它们截去不影响 \(s_{i - 1}\) 在 \(s_i\) 中的出现次数,并且 \(s_i\) 在 \(s_{i + 1}\) 中的出现次数可能增加,因此截去一定不劣,故而可以令 \(s_{i - 1}\) 必定为 \(s_i\) 的后缀。

结合 parent tree 的性质:祖先结点必定是后代结点的后缀,可以联想到在 parent tree 上跑一次从上到下的 dp 求最长链,dp 转移的条件是祖先结点代表的串在后代的串中至少出现 \(2\) 次。

如何判定转移的条件?

考虑到祖先串一定是后代串的后缀,即至少在后代串中出现 \(1\) 次。令 \(u, v\) 分别为祖先串和后代串,\(p\) 为 \(\operatorname{endpos}(v)\) 中任意元素,则当 \(\operatorname{endpos}(u)\) 中包含 \([p - len(v) + len(u), p - 1]\) 中任意元素时,\(u\) 在 \(v\) 中至少出现 \(2\) 次。

于是可以考虑用线段树合并维护每个结点的 \(\operatorname{endpos}\),判定可以 \(O(\log)\) 用线段树查询。

然而现在的问题是:parent tree 上的结点代表整个等价类,转移的时候应该用哪一个串?

结论 2:转移时等价类中所有串都是等价的。

考虑反证。假设 \(s\) 是某一结点 \(u\) 表示的等价类中最长的串,\(v\) 是结点 \(u\) 的祖先,串 1 和串 2 都是结点 \(v\) 的等价类中的串。

不妨假定串 1 是 \(\rm abcb\),串 2 是 \(\rm babcb\),串 \(s\) 是 \(\rm abcbabcb\),则串 \(1, s\) 之间可以转移而串 \(2, s\) 之间不可以转移。

如果存在另一个可以和串 2 转移的串 3,不妨假定为 \(\rm babcbabcb\),那么因为 \(s\) 是 \(u\) 等价类中最长的串,所以串 3 一定来自另一个以 \(v\) 为祖先的等价类中。

在上面的反例中,串 3 所在的等价类以 \(u\) 为祖先,记这个等价类为 \(w\)。根据 parent tree 的定义有:

\(|\operatorname{endpos}(u)| > |\operatorname{endpos}(w)|\)

也就是存在位置 \(p \in |\operatorname{endpos}(u)| - |\operatorname{endpos}(w)|\) 使得该位置存在串 \(s\) 而不存在串 3.

那么该位置出现了 \(2\) 次串 1 而只出现了 \(1\) 次串 \(2\),与串 1 和串 2 在同一等价类中矛盾,得证。

所以转移的时候只需要取最长串即可。

那么在 parent tree 上进行一次从上往下的 dp 求最长链,转移的时候用线段树判定即可。

代码

有亿 丶难调。

#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 2e5 + 5;
const int sam_sz = maxn << 1;
const int t_sz = 1e7 + 5;

inline int max(const int &a, const int &b) { return (a >= b ? a : b); }

int n;
int rt[sam_sz];
int dp[sam_sz], from[sam_sz];
char s[maxn];

namespace SGT
{
    int cnt;
    int ls[t_sz], rs[t_sz];

    int insert(int l, int r, int p)
    {
        int k = ++cnt;
        if (l == r) return k;
        int mid = (l + r) >> 1;
        if (p <= mid) ls[k] = insert(l, mid, p);
        else rs[k] = insert(mid + 1, r, p);
        return k;
    }

    int merge(int a, int b)
    {
        if ((!a) || (!b)) return a | b;
        int k = ++cnt;
        ls[k] = merge(ls[a], ls[b]);
        rs[k] = merge(rs[a], rs[b]);
        return k;
    }

    bool query(int k, int l, int r, int ql, int qr)
    {
        if (!k) return false;
        if ((l >= ql) && (r <= qr)) return true;
        int mid = (l + r) >> 1;
        if ((ql <= mid) && query(ls[k], l, mid, ql, qr)) return true;
        if ((qr > mid) && query(rs[k], mid + 1, r, ql, qr)) return true;
        return false;
    }
}

namespace SAM
{
    int cur, lst;
    int seq[sam_sz], pos[sam_sz], cnt[sam_sz];
    int len[sam_sz], fa[sam_sz], son[sam_sz][26];

    void copy_node(int to, int from)
    {
        len[to] = len[from], fa[to] = fa[from], pos[to] = pos[from];
        memcpy(son[to], son[from], sizeof(son[to]));
    }

    void insert(int c, int cp)
    {
        int p = lst, np = lst = ++cur;
        len[np] = len[p] + 1, pos[np] = cp;
        for ( ; ~p && (!son[p][c]); p = fa[p]) son[p][c] = np;
        if (!~p) fa[np] = 0;
        else
        {
            int q = son[p][c];
            if (len[q] == len[p] + 1) fa[np] = q;
            else
            {
                int nq = ++cur;
                copy_node(nq, q);
                len[nq] = len[p] + 1;
                fa[q] = fa[np] = nq;
                for ( ; ~p && (son[p][c] == q); p = fa[p]) son[p][c] = nq;
            }
        }
    }

    void sort()
    {
        for (int i = 1; i <= cur; i++) cnt[len[i]]++;
        for (int i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
        for (int i = 1; i <= cur; i++) seq[cnt[len[i]]--] = i;
    }

    void solve()
    {
        for (int i = 1, t = 0; i <= n; i++)
        {
            t = son[t][s[i] - 'a'];
            rt[t] = SGT::insert(1, n, i);
        }
        // printf("debug %d\n", seq[1]);
        for (int i = cur; i; i--) rt[fa[seq[i]]] = SGT::merge(rt[fa[seq[i]]], rt[seq[i]]);
        int ans = 1;
        for (int i = 1; i <= cur; i++)
        {
            int x = seq[i];
            if (!fa[x]) dp[x] = 1, from[x] = x;
            else
            {
                // puts("nmsl");
                int anc = from[fa[x]];
                // printf("[%d, %d]\n", anc, x);
                if (SGT::query(rt[anc], 1, n, pos[x] - len[x] + len[anc], pos[x] - 1)) dp[x] = dp[fa[x]] + 1, from[x] = x;
                else dp[x] = dp[fa[x]], from[x] = from[fa[x]];
            }
            ans = max(ans, dp[x]);
        }
        printf("%d\n", ans);
    }
}

int main()
{
    scanf("%d%s", &n, s + 1);
    SAM::fa[0] = -1;
    for (int i = 1; i <= n; i++) SAM::insert(s[i] - 'a', i);
    SAM::sort();
    SAM::solve();
    return 0;
}

标签:sz,sam,int,题解,len,son,fa,Slogans,CF700E
From: https://www.cnblogs.com/lingspace/p/cf700e.html

相关文章

  • 【题解】CF1073G Yet Another LCP Problem
    题面很清楚,不概括了。思路后缀树+树剖。套路题。看到lcp考虑转化成后缀树上求lca,这里可以用SAM构造反串的parenttree解撅(f**kuukk)于是问题转化成:每次给......
  • 【题解】CF808E Selling Souvenirs
    题意多重背包,但数据范围很大并且体积小于等于三。思路乱搞。很自然地考虑将物品按照体积分成三类。显然对于同一类的物品从最大开始取最优,那么有一个贪心的想法。直......
  • 【题解】P5305 [GXOI/GZOI2019]旧词
    题面很清楚,不概括题意了。思路树剖。\(k=1\)的情况是P4211[LNOI2014]LCA具体来说,只需要\(\forall1\leqi\leqx\),将\(1\)到\(i\)的路径上每一个结点权值......
  • 题解 校内考20230104 T2 旗木双翼
    题目描述菲菲和牛牛在一块\(n\)行\(m\)列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。落子......
  • LOJ 数列分块入门 9 题解题报告
    LOJ数列分块入门9题解题报告\(\text{ByDaiRuiChen007}\)I.数列分块入门1题目大意\(\text{Link}\)维护一个长度为\(n\)的序列,支持区间加,单点查值思路分析简......
  • IntelliJ IDEA常见问题解决办法汇总
     mac上idea升级到2020.2.2后,发现versioncontrol中的localchanges不见了!解决办法:View—>ToolWIndows—>Commit【点击下,就会提示要把这个Commit放在IDEA面板那个位置,选择......
  • git 不区分大小写问题解决
    使用vscode   更改vue文件为大驼峰的方式 发现本地代码和提交时的代码名称不同是因为git不区分大小写这时我们需要找到代码存放位置 鼠标右键  GitBashHer......
  • 异地多活回环同步问题解决方案
    1.异地多活:一般为两地三中心或者三地五中心,这样设计是为了在发生单点故障或网络分区时,集群能继续提供服务。两地三中心可以容忍机房级别灾难,三地五中心可以容忍城市级别灾......
  • Codeforces Hello 2023 CF 1779 A~F 题解
    点我看题A.HallofFame先把不用操作就合法的情况判掉。然后发现交换LL,RR,RL都是没用的,只有交换LR是有用的,这样可以照亮相邻的两个位置。所以我们就是要找到一个位置i,......
  • Codeforces Hello 2023 CF 1779 A~F 题解
    点我看题A.HallofFame先把不用操作就合法的情况判掉。然后发现交换LL,RR,RL都是没用的,只有交换LR是有用的,这样可以照亮相邻的两个位置。所以我们就是要找到一个位置i,......