LOJ#3326. 「SNOI2020」字符串 后缀树+贪心

本文探讨了如何使用后缀树和字符串匹配自动机(SAM)解决特定问题,即求两个字符串的所有前缀的最长公共前缀(LCP)之和的最大值。通过构建后缀树,利用贪心策略进行节点匹配,避免了对LCP长度的分类讨论,直接实现了最优解。代码示例展示了算法的具体实现过程。

问题可以转化为:$A$ 与 $B$ 所有前缀一一配对,LCP 之和最大是多少.  

构建后缀树,然后对于点 $x$,若 LCP 为 $x$ 则贡献就是 $x$ 子树中 $A$ 点和 $B$ 点较小数量.     

我们发现如果要求和最大,就贪心匹配.   

由于后缀树中点 $x$ 的长度为 mx[x] ~ mx[pre[x]],我们需要分类讨论 $LCP$ 的长度.  

但是由于题中特殊条件,导致后缀树中的关键点(A,B 匹配到的点)都表示前缀,而根据 SAM 原理,这些前缀长度都等于 mx[x].   

所以我们不用对 LCP 长度分类讨论,直接贪心即可.  

code:  

#include <bits/stdc++.h>      
#define N 170000  
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;     
ll ans;   
char A[N],B[N]; 
int n,K,tot,last,edges;  
int hd[N<<2],nex[N<<2],to[N<<2];  
int ch[N<<2][26],pre[N<<2],mx[N<<2],cnt[2][N<<2],c0[N<<2],c1[N<<2];         
void add(int u,int v) {  
    nex[++edges]=hd[u],hd[u]=edges,to[edges]=v;   
}
void extend(int c) {     
    if(ch[last][c]) {  
        int p=last,q=ch[last][c];  
        if(mx[q]==mx[p]+1) last=q;   
        else {   
            int nq=++tot;  
            mx[nq]=mx[p]+1;   
            memcpy(ch[nq],ch[q],sizeof(ch[q]));   
            pre[nq]=pre[q],pre[q]=nq;   
            for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;                            
            last=nq;  
        }
    } 
    else {    
        int np=++tot,p=last;  
        mx[np]=mx[p]+1,last=np;   
        for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;   
        if(!p) pre[np]=1;  
        else {  
            int q=ch[p][c];   
            if(mx[q]==mx[p]+1) pre[np]=q;    
            else {      
                int nq=++tot;  
                mx[nq]=mx[p]+1;  
                memcpy(ch[nq],ch[q],sizeof(ch[q]));   
                pre[nq]=pre[q],pre[np]=pre[q]=nq;           
                for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;    
            }
        }
    }
}
void dfs(int x) {    
    int y,z;         
    c0[x]=cnt[0][x];  
    c1[x]=cnt[1][x];   
    for(int i=hd[x];i;i=nex[i]) { 
        y=to[i],dfs(y);   
        c0[x]+=c0[y];  
        c1[x]+=c1[y];  
    }       
    int det=min(c0[x],c1[x]);  
    ans+=1ll*min(mx[x],K)*det;         
    c0[x]-=det;  
    c1[x]-=det;   
}
int main() {  
    // setIO("input");       
    scanf("%d%d",&n,&K);             
    scanf("%s%s",A+1,B+1);    
    last=tot=1; 
    for(int i=n;i>=1;--i) {  
        extend(A[i]-'a');  
        if(i<=n-K+1) ++cnt[0][last]; 
    }   
    last=1;   
    for(int i=n;i>=1;--i) {  
        extend(B[i]-'a');                      
        if(i<=n-K+1) ++cnt[1][last]; 
    }        
    for(int i=2;i<=tot;++i) {   
        add(pre[i],i);  
    }    
    dfs(1);  
    printf("%lld\n",1ll*K*(n-K+1)-ans);    
    return 0; 
}

  

可并堆是一种支持合并操作的堆数据结构,常见的可并堆有左偏树、斜堆、二项堆等。对于 LOJ#P188 可并堆的问题,下面以左偏树为例给出解题思路代码实现。 ### 解题思路 1. **左偏树的性质**: - 左偏树是一种可并堆,它满足堆性质(小根堆或大根堆),即每个节点的值小于(或大于)其子节点的值。 - 左偏树还满足左偏性质,即每个节点的左子树的距离(到最近的叶子节点的距离)不小于右子树的距离。 2. **合并操作**: - 合并两个左偏树时,比较两个根节点的值,将值较大的根节点的树合并到值较小的根节点的右子树中。 - 合并后,检查右子树的距离是否大于左子树的距离,如果是,则交换左右子树,以维护左偏性质。 3. **插入操作**: - 插入一个新节点可以看作是合并一个只有一个节点的左偏树原左偏树。 4. **删除操作**: - 删除根节点后,将其左右子树合并成一个新的左偏树。 ### 代码实现 ```python class Node: def __init__(self, val): self.val = val self.left = None self.right = None self.dist = 0 def merge(x, y): if not x: return y if not y: return x if x.val > y.val: x, y = y, x x.right = merge(x.right, y) if not x.left or (x.right and x.left.dist < x.right.dist): x.left, x.right = x.right, x.left x.dist = (x.right.dist + 1) if x.right else 0 return x def insert(root, val): new_node = Node(val) return merge(root, new_node) def delete(root): return merge(root.left, root.right) # 示例使用 root = None root = insert(root, 3) root = insert(root, 1) root = insert(root, 5) print(root.val) # 输出堆顶元素 root = delete(root) print(root.val) # 输出删除堆顶元素后的堆顶元素 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值