1. 程式人生 > 其它 >P6793-[SNOI2020]字串【廣義SAM,貪心】

P6793-[SNOI2020]字串【廣義SAM,貪心】

技術標籤:貪心字串luoguSNOI廣義SAM貪心

正題

題目連結:https://www.luogu.com.cn/problem/P6793


題目大意

給出兩個長度為 n n n的字串,取出他們所有長度為 k k k的連續子串分別構成兩個可重集合 A , B A,B A,B

你每次可以花費 x x x點代價修改 A A A中一個字串長度為 x x x的字尾,求至少花費多少代價能夠使得兩個集合完全相同。

1 ≤ k ≤ n ≤ 1.5 × 1 0 5 1\leq k\leq n\leq 1.5\times 10^5 1kn1.5×105


解題思路

兩個串 S , T S,T

S,T的匹配代價是 m a x { k − L C P ( S , T ) , 0 } max\{k-LCP(S,T),0\} max{kLCP(S,T),0}

這個和之前有道題很像,沿用想法就是在後綴樹上搞。

兩個點的 L C P LCP LCP可以在他們字尾樹上的 L C A LCA LCA處得到。

現在問題就變為了有一些黑白點,知道兩個點匹配的代價與 L C A LCA LCA的關係,求最小代價和。

基礎貪心?直接在深度小的地方合併完就好了。

字尾樹就是把反串跑廣義SAM就好了

時間複雜度 O ( n ) O(n) O(n)


code

#include<cstdio>
#include
<cstring>
#include<algorithm> #define ll long long using namespace std; const ll N=6e5+10; struct node{ ll to,next; }a[N]; ll n,k,tot,ls[N],v[N][2],ans; ll ch[N][26],fa[N],len[N],cnt; char sa[N],sb[N]; ll Insert(ll p,ll c){ if(ch[p][c]){ ll q=ch[p][c]; if(len[p]+1==len[q])return q; ll nq=
++cnt;len[nq]=len[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[nq])); fa[nq]=fa[q];fa[q]=nq; for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq; return nq; } ll np=++cnt;len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np; if(!p)fa[np]=1; else{ ll q=ch[p][c]; if(len[p]+1==len[q])fa[np]=q; else{ ll nq=++cnt;len[nq]=len[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[nq])); fa[nq]=fa[q];fa[q]=fa[np]=nq; for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq; } } return np; } void addl(ll x,ll y){ a[++tot].to=y; a[tot].next=ls[x]; ls[x]=tot;return; } void dfs(ll x){ for(ll i=ls[x];i;i=a[i].next){ ll y=a[i].to;dfs(y); v[x][0]+=v[y][0]; v[x][1]+=v[y][1]; } ll tmp=min(v[x][0],v[x][1]); ans+=max(k-len[x],0ll)*tmp; v[x][0]-=tmp;v[x][1]-=tmp; return; } signed main() { scanf("%lld%lld",&n,&k); scanf("%s",sa+1); scanf("%s",sb+1); ll last=cnt=1; for(ll i=n;i>=1;i--) last=Insert(last,sa[i]-'a'),v[last][0]+=((n-i+1)>=k); last=1; for(ll i=n;i>=1;i--) last=Insert(last,sb[i]-'a'),v[last][1]+=((n-i+1)>=k); for(ll i=2;i<=cnt;i++)addl(fa[i],i); dfs(1); printf("%lld\n",ans); return 0; }