CodeForces 128B [ 后缀树 ]

虽然是B题,正解是很简单的优先队列,但还是拿来当后缀树模板题敲了一下,O(N)求第K大子串;

这题只求了一个第K大串,但实际上,多个询问应该也是能搞的:

和后缀数组相仿,在后缀树上两端都是非叶子节点的边直接按 边上记录的[L,R]进行枚举遍历,是O(N)复杂度的,在遍历时用一个栈存储串的信息,是可以做到的。

然后就是第K大串可能在叶子节点向上的连边中,这时候因为这条边的一个字符的价值只有1(价值代表单位字符每多一个count增大多少,这里的count代表现在枚举到的串是第几大,价值为这条边底下的叶子节点数),所以可以直接算,或者打个标记之类的,就可以得到啦~

所以:如果询问的第K大串是有重复的(即这个串在所有子串出现了至少两次),那么询问M次,其复杂度是 O(MAX(size*N,M)),size是字符集大小;

如果有单一的存在,复杂度为O(max(Σ(单一串长),原来的复杂度));


总结了这么多,这题的代码如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
#include<stdlib.h>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<bitset>
#pragma comment(linker, "/STACK:1024000000,1024000000")
template <class T>
bool scanff(T &ret){ //Faster Input
    char c; int sgn; T bit=0.1;
    if(c=getchar(),c==EOF) return 0;
    while(c!='-'&&c!='.'&&(c<'0'||c>'9')) c=getchar();
    sgn=(c=='-')?-1:1;
    ret=(c=='-')?0:(c-'0');
    while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
    if(c==' '||c=='\n'){ ret*=sgn; return 1; }
    while(c=getchar(),c>='0'&&c<='9') ret+=(c-'0')*bit,bit/=10;
    ret*=sgn;
    return 1;
}
#define inf 1073741823
#define llinf 4611686018427387903LL
#define PI acos(-1.0)
#define lth (th<<1)
#define rth (th<<1|1)
#define rep(i,a,b) for(int i=int(a);i<=int(b);i++)
#define drep(i,a,b) for(int i=int(a);i>=int(b);i--)
#define gson(i,root) for(int i=ptx[root];~i;i=ed[i].next)
#define tdata int testnum;scanff(testnum);for(int cas=1;cas<=testnum;cas++)
#define mem(x,val) memset(x,val,sizeof(x))
#define mkp(a,b) make_pair(a,b)
#define findx(x) lower_bound(b+1,b+1+bn,x)-b
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;


#define SIZE 27
struct suffixtree{
        struct node{
        int l,r,point,a[SIZE];
        void init(){
            mem(a,0);
            point=l=0;
            r=-1;
        }
    }T[400011];
    char s[400011];
    int actnode,actride,actline;
    int rest,total,temp,linker,len;
    void builtleaf(int root,int line){
        total++;
        T[total].init();
        T[total].l=len;
        T[total].r=100000001;
        T[root].a[line]=total;
        rest--;
    }
    bool pd_ride(int next){
        temp=T[next].r-T[next].l+1;
        if(actride>=temp){
            actride-=temp;
            actnode=next;
            actline+=temp;
            return true;
        }
        return false;
    }
    void link(int x){
        if(linker>0)T[linker].point=x;
        linker=x;
    }
    void insert(int wait){
        s[++len]=wait;
        rest++;
        linker=0;
        while(rest>0){
            if(actride==0)actline=len;
            if(T[actnode].a[s[actline]]==0){ 
                builtleaf(actnode,s[actline]);
                link(actnode);
            }
            else{
                int next=T[actnode].a[s[actline]];
                if(pd_ride(next))continue;
                if(s[T[next].l+actride]==wait){
                    actride++;
                    link(actnode);
                    break;
                }  
                total++;
                T[total].init();
                T[actnode].a[s[actline]]=total;
                T[total].l=T[next].l;
                T[total].r=T[next].l+actride-1;
                T[next].l+=actride;
                T[total].a[s[T[next].l]]=next;
                link(total);
                builtleaf(total,wait);
            }
            if(actnode==0&&actride>0){
                actride--;
                actline=len-rest+1;
            }
            else actnode=T[actnode].point;
        }
    }
    void clear(){
        total=0;
        len=0;
        T[0].init();
        actnode=actride=actline=0;
    }
    void debug(){
        rep(i,0,total){
            printf("T[%d] (%d %d)\n",i,T[i].l,T[i].r);
        }
    }
}st; 

#define NN 400400
char s[NN];
ll cot[NN];
ll sum;
ll k;
int len;

ll getcot(int x){
    ll temp=0;
    ll bj=1;
    rep(i,0,26){
        if(st.T[x].a[i]){
            bj=0;
            temp+=getcot(st.T[x].a[i]);
        }
    }
    cot[x]=temp+bj;
    return cot[x];
}
ll edr,edx;
int alen=0;
char ans[NN];
void dfs(int x){
    sum+=(ll)(min(st.T[x].r,len)-st.T[x].l+1)*cot[x];
    if(sum>=k){
        edx=x;
        edr=min(ll(st.T[x].r),ll(len));
        while(sum-cot[x]>=k){
            sum-=cot[x];
            edr--;
        }
        //printf("%lld %lld\n",edx,edr);
        //return;
    }
    rep(i,0,26){
        if(st.T[x].a[i]&&sum<k){
            dfs(st.T[x].a[i]);
        }
    }
    if(sum>=k){
        drep(i,min(edr,ll(st.T[x].r)),st.T[x].l){
            ans[alen++]=s[i];
        }
    }
}




main(){
    st.clear();
    scanf("%s",s+1);
    len=strlen(s+1);
    rep(i,1,len)st.insert(s[i]-'a');
    st.insert(26);
    scanf("%lld",&k);
    if(ll(len)*ll(len+1)/2LL<k){
        printf("No such line.\n");
        return 0;
    }
    getcot(0);
    dfs(0);
    ans[alen]=0;
    reverse(ans,ans+alen);
    printf("%s\n",ans);
}



引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[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^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值