zoukankan      html  css  js  c++  java
  • CF700E Cool Slogans SAM、线段树合并、树形DP

    传送门


    在最优的情况下,序列(s_1,s_2,...,s_k)中,(s_i (i in [2 , k]))一定会是(s_{i-1})的一个(border),即(s_i)同时是(s_{i-1})的前缀和后缀,否则一定可以通过减去(s_{i-1})的一个前缀和后缀使得满足条件。

    对原串建立(SAM),因为有互为后缀的条件,所以(s_1,s_2,...,s_k)会对应(parent)树一条链上的若干状态。

    发现可以在(parent)树上DP。设(f_i)表示到达(i)状态时序列的最长长度,转移看它祖先中(f)最大且长度最短的串是否在当前串中出现了至少(2)次。

    判断(A)是否在(B)中出现了至少两次也不是很麻烦。处理出(A,B)状态的任意一个(endpos),记做(pos_A,pos_B),然后用线段树合并得到(A,B)状态的(endpos)集合,那么(A)(B)中出现了至少两次意味着(A)(endpos)集合与([pos_B - len_B + len_A , pos_B])的交集的大小(geq 2)。注意到我们需要求的(AB)满足(A)(B)的一个后缀,所以只需要判断(A)(endpos)集合与([pos_B - len_B + len_A , pos_B))是否有交就可以了。

    注意:(SAM)的一个状态中可能有多个串,但是题目中,因为某个串出现,同一状态的其他串也一定会在同一位置出现,所以这些串是等价的,直接取每个状态的最长串即可。因此,可能会存在选出的(s_i)不是(s_{i-1})(border),但并不会影响答案的大小。

    #include<iostream>
    #include<cstdio>
    #include<cctype>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<vector>
    //This code is written by Itst
    using namespace std;
    
    const int MAXN = 4e5 + 7;
    
    namespace segtree{
        struct node{
            int l , r , sz;
        }Tree[MAXN << 5];
        int rt[MAXN] , cnt;
    
    #define lch Tree[x].l
    #define rch Tree[x].r
    #define mid ((l + r) >> 1)
        int insert(int t , int l , int r , int tar){
            int x = ++cnt;
            Tree[x] = Tree[t];
            ++Tree[x].sz;
            if(l == r) return x;
            if(mid >= tar) lch = insert(lch , l , mid , tar);
            else rch = insert(rch , mid + 1 , r , tar);
            return x;
        }
    
        int merge(int p , int q){
            if(!p || !q) return p + q;
            int x = ++cnt;
            Tree[x].sz = Tree[q].sz + Tree[p].sz;
            lch = merge(Tree[p].l , Tree[q].l);
            rch = merge(Tree[p].r , Tree[q].r);
            return x;
        }
    
        bool query(int x , int l , int r , int L , int R){
            if(!Tree[x].sz) return 0;
            if(l >= L && r <= R) return 1;
            if(mid >= L && query(lch , l , mid , L , R)) return 1;
            return mid < R && query(rch , mid + 1 , r , L , R);
        }
    }
    
    using segtree::rt; using segtree::merge; using segtree::query;
    
    namespace SAM{
        int Lst[MAXN] , Sst[MAXN] , fa[MAXN] , trans[MAXN][26] , endpos[MAXN];
        int cnt = 1 , lst = 1 , L;
        char s[MAXN];
    
        void insert(int len , int x){
            int t = ++cnt , p = lst;
            endpos[t] = Lst[lst = t] = len;
            while(p && !trans[p][x]){
                trans[p][x] = t;
                p = fa[p];
            }
            if(!p){Sst[t] = fa[t] = 1; return;}
            int q = trans[p][x];
            Sst[t] = Lst[p] + 2;
            if(Lst[q] == Lst[p] + 1){fa[t] = q; return;}
            int k = ++cnt;
            memcpy(trans[k] , trans[q] , sizeof(trans[k]));
            Lst[k] = Lst[p] + 1; Sst[k] = Sst[q];
            Sst[q] = Lst[p] + 2;
            fa[k] = fa[q]; fa[q] = fa[t] = k;
            while(trans[p][x] == q){
                trans[p][x] = k;
                p = fa[p];
            }
        }
    
        void init(){
            scanf("%d %s" , &L , s + 1);
            for(int i = 1 ; i <= L ; ++i)
                insert(i , s[i] - 'a');
        }
    
        vector < int > ch[MAXN];
        int ans = 0 , top[MAXN] , len[MAXN];
        
        void dfs(int x){
            if(endpos[x]) rt[x] = segtree::insert(rt[x] , 1 , L , endpos[x]);
            for(auto t : ch[x]){
                dfs(t);
                if(!endpos[x]) endpos[x] = endpos[t];
                rt[x] = merge(rt[x] , rt[t]);
            }
        }
    
        void dp(int x){
            if(fa[x])
                if(fa[x] == 1 || query(rt[top[fa[x]]] , 1 , L , endpos[x] - Lst[x] + Lst[top[fa[x]]] , endpos[x] - 1)){
                    len[x] = len[fa[x]] + 1;
                    top[x] = x;
                    ans = max(ans , len[x]);
                }
                else{
                    len[x] = len[fa[x]];
                    top[x] = top[fa[x]];
                }
            for(auto t : ch[x]) dp(t);
        }
        
        void work(){
            for(int i = 2 ; i <= cnt ; ++i)
                ch[fa[i]].push_back(i);
            dfs(1);
            dp(1);
            cout << ans;
        }
    }
    
    int main(){
        #ifndef ONLINE_JUDGE
        freopen("in" , "r" , stdin);
        //freopen("out" , "w" , stdout);
        #endif
        SAM::init(); SAM::work();
        return 0;
    }
    
  • 相关阅读:
    MongoDB下配置用户权限
    (CF)Codeforces445A DZY Loves Chessboard(纯实现题)
    C语言概述
    C#中值类型和引用类型的差别浅记
    Qt5官方demo解析集30——Extending QML
    汉澳sinox通过ndis执行windows驱动程序
    linux设备驱动归纳总结(三):4.ioctl的实现【转】
    linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现【转】
    linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write【转】
    linux设备驱动归纳总结(三):1.字符型设备之设备申请【转】
  • 原文地址:https://www.cnblogs.com/Itst/p/10451247.html
Copyright © 2011-2022 走看看