「雅禮集訓 2017 Day7」事情的相似度
求區間內兩個前綴的最長公共後綴長度的最大值。
後綴自動機,用維護樹。
離線,從前到後每次加入一個前綴,將這個前綴在後綴自動機上的點。
發現這是一個每次將樹上一個點到根的路徑染色的形式。
時維護每個點被最後一次染色的時間。
當新的一次抵達一個點時,這個的最後一次染色和這次染色可以看做這兩個前綴的到根的路徑包含。
直接對於最後一次染色的時間在樹狀數組上對的字符串長度取。
處理到前綴時在樹狀數組上回答所有的詢問即可。
#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,m;
namespace BIT{
int tr[maxn];
void upd(int u,int v){ for(;u;u-=u&-u) tr[u]=max(tr[u],v); }
int qry(int u){ int r=0;for(;u<=n;u+=u&-u) r=max(r,tr[u]);return r; }
}
char s[maxn];
int fa[maxn],ch[maxn][2],tr[maxn][2],tg[maxn],pos[maxn],len[maxn],tot=1,last=1;
void ins(int c){ // rooted 1
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;p && !tr[p][c];p=fa[p]) tr[p][c]=u;
if(!p) fa[u] = 1;
else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
else{
int v = ++tot;
tr[v][0] = tr[q][0],
tr[v][1] = tr[q][1],
len[v] = len[p] + 1,
fa[v] = fa[q];
for(;p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[u] = fa[q] = v;
}
}
#define pa fa[x]
int inr(int x){ return ch[pa][1] == x; }
int isr(int x){ return ch[pa][0] ^ x && ch[pa][1] ^ x; }
void dt(int x){
if(ch[x][0]) tg[ch[x][0]] = tg[x];
if(ch[x][1]) tg[ch[x][1]] = tg[x];
}
void dtpath(int x){ if(!isr(x)) dtpath(pa);dt(x); }
void rot(int x){
int y = fa[x] , z = fa[y] , c = inr(x);
if(!isr(y)) ch[z][inr(y)] = x;
(ch[y][c] = ch[x][!c]) && (fa[ch[y][c]] = y);
fa[fa[ch[x][!c] = y] = x] = z;
}
void splay(int x){
for(dtpath(x);!isr(x);rot(x))
if(!isr(pa))
rot(inr(pa) ^ inr(x) ? x : pa);
}
int access(int x,int id){
int y = 0;
for(;x;x=fa[y=x]){
splay(x);
if(tg[x])
BIT::upd(tg[x] , len[x]);
ch[x][1] = y;
tg[x] = id;
}
return y;
}
vector<int>G[maxn];
int l[maxn],r[maxn],ans[maxn];
int main(){
scanf("%d%d",&n,&m);
scanf("%s",s+1);
rep(i,1,n) ins(s[i]-'0'),pos[i] = last;
rep(i,1,m) scanf("%d%d",&l[i],&r[i]),G[r[i]].push_back(i);
rep(i,1,n){
access(pos[i],i);
rep(j,0,G[i].size()-1){
int v = G[i][j];
ans[v] = BIT::qry(l[v]);
}
}
rep(i,1,m) printf("%d\n",ans[i]);
}
「十二省聯考 2019」字符串問題
給定一個字符串,給出個點,每個點是字符串中的一個子串,每個點有若干類邊,每類邊可以被表示爲該點連向 包含字符串某一個子串作爲前綴 的點,求最長路或判斷沒有最長路。
發現作爲前綴這個條件,可以簡單的對反串建後綴自動機,則連上樹中父親到兒子的有向邊即可表示作爲前綴這個條件。
但是有一個細節上的問題就是後綴自動機的點是多個不同的串,在後綴自動機上是同一個點的串有時需要連邊有時又不需要連邊,這個問題很好解決,就對於每個點存一下所有串,按長度排個序再連邊即可。
注意後綴自動機上串定位可以用倍增實現。
#include<bits/stdc++.h>
#define maxn 800005
#define maxc 26
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 19
using namespace std;
int n;
char S[maxn];
int fa[maxn],f[lim][maxn],tr[maxn][maxc],len[maxn],pos[maxn],tot,last;
void ins(int c){
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;~p && tr[p][c]==0;p=fa[p]) tr[p][c] = u;
if(p==-1) fa[u] = 0;
else if(len[q=tr[p][c]] == len[p] + 1) fa[u] = q;
else{
int v = ++tot;
memcpy(tr[v],tr[q],sizeof tr[q]);
fa[v] = fa[q] , len[v] = len[p] + 1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = fa[u] = v;
}
}
vector<int>G[maxn];int Le[maxn],na,nb;
LL dis[maxn];
int info[maxn],Prev[maxn<<2],to[maxn<<2],cst[maxn<<2],IN[maxn],cnt_e,cnt_p;
void Node(int u,int v,int c=0){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c,IN[v]++; }
int in[maxn],ot[maxn];
bool cmp(const int &u,const int &v){ return Le[u] == Le[v] ? u > v : Le[u] < Le[v]; }
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
int T;
fa[0] = -1;
for(scanf("%d",&T);T--;){
scanf("%s",S+1);
n = strlen(S+1);
reverse(S+1,S+n+1);
rep(i,1,n) ins(S[i]-'a'),pos[i]=last;
rep(i,1,tot) f[0][i] = fa[i];
rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
scanf("%d",&na);
rep(i,1,na){
int l,r;
scanf("%d%d",&l,&r);
swap(l,r);
l = n - l + 1 , r = n - r + 1;
Le[i] = r - l + 1;
int u = pos[r];
per(j,lim-1,0) if(len[f[j][u]] >= Le[i])
u = f[j][u];
G[u].push_back(i);
}
scanf("%d",&nb);
rep(i,1,nb){
int l,r;
scanf("%d%d",&l,&r);
swap(l,r);
l = n - l + 1 , r = n - r + 1;
Le[i+na] = r - l + 1;
int u = pos[r];
per(j,lim-1,0) if(len[f[j][u]] >= Le[i+na])
u = f[j][u];
G[u].push_back(i+na);
}
cnt_p = na + nb;
rep(i,1,tot){
sort(G[i].begin(),G[i].end(),cmp);
in[i] = ++cnt_p;
int p = in[i];
rep(j,0,G[i].size()-1){
if(G[i][j] <= na)
Node(p,G[i][j],Le[G[i][j]]);
else
Node(p,G[i][j]),p=G[i][j];
}
ot[i] = p;
}
++cnt_p;
rep(i,1,tot) if(fa[i]) Node(ot[fa[i]],in[i]);
rep(i,1,na) Node(cnt_p,i,Le[i]);
int m;scanf("%d",&m);
rep(i,1,m){
int x,y;scanf("%d%d",&x,&y);
Node(x,y+na);
}
queue<int>q;
rep(i,1,cnt_p) if(!IN[i]) q.push(i),dis[i]=0;
else dis[i] = -0x3f3f3f3f3f3f3f3fll;
LL ans = 0;
for(int u;!q.empty();){
u=q.front(),q.pop();
ans = max(ans , dis[u]);
for(int j=info[u],v;j;j=Prev[j]){
v = to[j];
dis[v] = max(dis[v] , dis[u] + cst[j]);
if((--IN[v]) == 0) q.push(v);
}
}
rep(i,1,cnt_p) if(IN[i]) ans = -1;
printf("%lld\n",ans);
memset(tr,0,sizeof(tr[0])*(tot+1));
rep(i,1,tot) fa[i] = 0 , G[i].clear();
rep(i,1,cnt_p) info[i] = IN[i] = 0;
last = tot = cnt_e = 0;
}
}
「雅禮集訓 2017 Day1」字符串
給出一個字符串,長度爲,組,一個固定的,
次詢問每次給出一個長度爲的字符串,求在中出現次數的總和。
發現正常的做法,是瓶頸,考慮設計一個和無關的算法。
設定閾值,當時,我們暴力算出所有在中的出現次數並暴力統計,複雜度(這裏其實應該還需要兩次的,但是因爲是個詢問平攤到個位置中(顯然平攤時操作次數最多),所以是,實際上是在靠近時完全可以忽略(這也是下文複雜度分析的基礎),在更小時就算是也不足爲慮。)
當時,我們在後綴自動機上定位計算在中出現次數,複雜度爲
平衡一下複雜度,
時最優爲:
(實際表現十分好。)
#include<bits/stdc++.h>
#define maxn 200005
#define S 1300
#define maxc 26
#define lim 18
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,m,q,K;
char s[maxn],w[maxn];
int fa[maxn],tr[maxn][maxc],len[maxn],sm[maxn],tot,last,pos[maxn],l[maxn],r[maxn],c[maxn],f[lim][maxn];
vector<int>G[S+5][S+5];
void ins(int c){
int u = ++tot , p =last , q;
len[last = u] = len[p] + 1;
for(;~p && !tr[p][c];p=fa[p]) tr[p][c] = u;
if(p == -1) fa[u] = 0;
else if(len[q=tr[p][c]]==len[p]+1) fa[u] = q;
else{
int v = ++tot;
memcpy(tr[v],tr[q],sizeof tr[q]);
fa[v] = fa[q] , len[v] = len[p] + 1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = fa[u] = v;
}
sm[u]++;
}
void radix_sort(int *a,int *sa,int n,int m){
static int c[maxn]={};
rep(i,0,m) c[i] = 0;
rep(i,0,n) c[a[i]]++;
rep(i,1,m) c[i] += c[i-1];
per(i,n,0) sa[--c[a[i]]] = i;
}
int main(){
scanf("%d%d%d%d",&n,&m,&q,&K);
fa[0] = -1;
scanf("%s",s);
rep(i,0,n-1) ins(s[i] - 'a') , pos[i] = last;
rep(i,0,tot) c[i] = i;
radix_sort(len,c,tot,n);
per(i,tot,1) sm[fa[c[i]]] += sm[c[i]];
rep(i,0,m-1) scanf("%d%d",&l[i],&r[i]);
if(K <= S){
rep(i,0,m-1) G[l[i]][r[i]].push_back(i);
for(int a,b;q--;){
scanf("%s",w);
scanf("%d%d",&a,&b);
LL ans = 0;
rep(i,0,K-1){
int u = 0 , nl = 0;
rep(j,i,K-1){
int v = w[j] - 'a';
for(;~u && !tr[u][v];u = fa[u]);
if(u == -1) nl = 0 , u = 0;
else nl = min(nl + 1 , len[u] + 1) , u = tr[u][v];
if(nl < j-i+1) break;
ans += 1ll * sm[u] * (upper_bound(G[i][j].begin(),G[i][j].end(),b) - lower_bound(G[i][j].begin(),G[i][j].end(),a));
}
}
printf("%lld\n",ans);
}
}
else{
rep(i,1,tot) f[0][i] = fa[i];
rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
for(int a,b;q--;){
scanf("%s",w);
scanf("%d%d",&a,&b);
LL ans = 0;
int u = 0 , nl = 0;
static int ps[maxn],ln[maxn];
rep(j,0,K-1){
int v = w[j] - 'a';
for(;~u && !tr[u][v];u = fa[u]);
if(u == -1) nl = 0 , u = 0;
else nl = min(nl + 1 , len[u] + 1) , u = tr[u][v];
ps[j] = u , ln[j] = nl;
}
rep(i,a,b) if(r[i] - l[i] + 1 <= ln[r[i]]){
int u = ps[r[i]];
per(j,lim-1,0) if(len[f[j][u]] >= r[i] - l[i] + 1)
u = f[j][u];
ans += sm[u];
}
printf("%lld\n",ans);
}
}
}
CF666E Forensic Examination
給出一個串和字符串數組,每次詢問在中的哪個數組中出現次數最多,多解輸出最靠前的那個。
建和的廣義後綴自動機,把的每個前綴在自動機上對應的點上插入,然後線段樹合併上傳。
每次詢問就是定位串之後簡單線段樹查詢即可。
#include<bits/stdc++.h>
#define maxn 2000005
#define lim 20
#define maxc 26
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
char Start;
int n,m;
char S[maxn],T[maxn];
int fa[maxn],tr[maxn][maxc],len[maxn],rt[maxn],pos[maxn],tot,last,f[lim][maxn];
vector<int>G[maxn];
void ins(int c,bool debug = 0){
if(tr[last][c]){
int p = last , q = tr[last][c];
if(len[q] != len[p] + 1){
int v=++tot;
memcpy(tr[v],tr[q],sizeof tr[q]);
fa[v]=fa[q],len[v]=len[p]+1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = v;
}
}
else{
int u=++tot,p=last,q;
len[u]=len[p]+1;
for(;~p && !tr[p][c];p=fa[p]) tr[p][c]=u;
if(p==-1) fa[u]=0;
else if(len[q=tr[p][c]]==len[p]+1) fa[u]=q;
else{
int v=++tot;
memcpy(tr[v],tr[q],sizeof tr[q]);
fa[v]=fa[q],len[v]=len[p]+1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = fa[u] = v;
}
}
}
#define maxp maxn *10
int lc[maxp],rc[maxp],mx[maxp],mxp[maxp];
void upd(int u){
if(mx[lc[u]] >= mx[rc[u]]) mx[u] = mx[lc[u]] , mxp[u] = mxp[lc[u]];
else mx[u] = mx[rc[u]] , mxp[u] = mxp[rc[u]];
}
void ins(int &u,int l,int r,int p){
if(!u) u = ++tot , mxp[u] = l;
if(l==r) return (void)(mx[u]++);
int m = l+r>>1;
p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
upd(u);
}
void merge(int &u,int l,int r){
if(!l || !r) return (void)(u=l+r);
u = ++tot;
if(lc[l] || rc[l]){
merge(lc[u],lc[l],lc[r]),merge(rc[u],rc[l],rc[r]);
upd(u);
}
else{
mx[u] = mx[l] + mx[r] , mxp[u] = mxp[l];
}
}
pair<int,int> qry(int u,int l,int r,int ql,int qr){
if(ql>r||l>qr||!u||ql>qr) return make_pair(0,0x3f3f3f3f);
if(ql<=l&&r<=qr) return make_pair(mx[u],mxp[u]);
int m = l+r>>1;
pair<int,int>r1 = qry(lc[u],l,m,ql,qr) , r2 = qry(rc[u],m+1,r,ql,qr);
if(r1.first >= r2.first) return r1;
else return r2;
}
void dfs(int u){
for(int v:G[u])
dfs(v),merge(rt[u],rt[u],rt[v]);
}
char End;
int main(){
// cerr << (&End - &Start) / 1024 / 1024 << endl;
scanf("%s",S+1);
n = strlen(S+1);
fa[0] = -1;
rep(i,1,n) ins(S[i]-'a'),pos[i]=last=tr[last][S[i]-'a'];
scanf("%d",&m);
rep(i,1,m){
scanf("%s",T+1);
int L = strlen(T+1);
last = 0;
rep(j,1,L){
ins(T[j]-'a',(j == 13 && i == 2)),last=tr[last][T[j]-'a'];
ins(rt[last],1,m,i);
}
}
rep(i,1,tot) G[fa[i]].push_back(i), f[0][i] =fa[i];
rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
dfs(0);
int q;scanf("%d",&q);
for(int l,r,pl,pr;q--;){
scanf("%d%d%d%d",&l,&r,&pl,&pr);
int u = pos[pr];
per(i,lim-1,0) if(len[f[i][u]] >= pr-pl+1)
u=f[i][u];
pair<int,int>R = qry(rt[u],1,m,l,r);
if(R.first == 0)R.second = l;
printf("%d %d\n",R.second,R.first);
}
}
CF1276F Asterisk Substrings
求將字符串的每一位分別替換爲一個新字符後,所有的字符串(包括)中所有子串的並的本質不同串的個數。
注意到答案是,,,,,五種構成的。
而除了以外都可以簡單計算。
對於,發現對於相同的,可選的都是一樣的。
所以想到在後綴自動機上每個節點求出它作爲時可選的的個數。
如果的是集合,那麼可選的的個數就是所有的也就是後移兩位後的所有後綴的子串的並的不同串的個數。
計算多個後綴的子串的並的不同串個數是一個可以用後綴數組解決的經典問題,將後綴排序後答案即爲所有後綴的長度減去相鄰後綴的。
在樹上啓發式合併即可簡單維護。
突然意識到既然複雜度是nlog2n的那我爲什麼不全程哈希求後綴數組和height呢
於是我們開始思考能不能不用後綴數組。
計算多個後綴的子串的並的不同串個數是一個可以用後綴自動機解決的經典問題,對反串建立後綴自動機後將需要求並的後綴在自動機上的點到根求個鏈並長度即爲答案(注意到在反串的後綴自動機中一個後綴一定是它所在點的最長串,所以不需要扣任何細節。),求鏈並可以用線段樹維護,具體是因爲把所有點按序排序後,鏈併爲所有點的深度和減去相鄰點的的深度和,那麼按序建線段樹就可以用線段樹合併維護答案,左右兒子合併時只需要求左邊的答案加右邊的答案減去左邊最靠右節點和右邊最靠左節點的深度。
所以我們就得到了只用後綴自動機的做法。怎麼感覺後綴自動機吊打後綴數組不留一點餘地的。
#include<bits/stdc++.h>
#define maxn 200005
#define maxc 26
#define lim 19
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n;
char S[maxn];
LL ans;
int st[maxn],tim,mn[lim][maxn<<1],dep[maxn],lg[maxn<<1];
int Lca(int u,int v){// u ,v is dfn
if(!u || !v) return 0;
if(u > v) swap(u,v);
int t = lg[v-u+1];
return dep[mn[t][u]] < dep[mn[t][v-(1<<t)+1]] ? mn[t][u] : mn[t][v-(1<<t)+1];
}
#define maxp maxn * 30
int rt[maxn],lc[maxp],rc[maxp],ml[maxp],mr[maxp],tot;
LL sm[maxp];
void upd(int u){
sm[u] = sm[lc[u]] + sm[rc[u]] - dep[Lca(ml[rc[u]] , mr[lc[u]])];
ml[u] = ml[lc[u]] ? ml[lc[u]] : ml[rc[u]];
mr[u] = mr[rc[u]] ? mr[rc[u]] : mr[lc[u]];
}
void ins(int &u,int l,int r,int p){
if(!u) u = ++tot;
if(l==r) return (void)(ml[u]=mr[u]=p,sm[u]=dep[mn[0][p]]);
int m =l+r>>1;
p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
upd(u);
}
void merge(int &u,int l,int r){
if(!l||!r) return (void)(u=l+r);
u = ++tot;
if(lc[l] || rc[l]){
merge(lc[u],lc[l],lc[r]) , merge(rc[u],rc[l],rc[r]);
upd(u);
}
else{
// no else!
assert(0);
}
}
struct SAM{
int tr[maxn][maxc] , fa[maxn] , len[maxn] , pos[maxn] , tot , last;
vector<int>G[maxn];
SAM(){
memset(tr,0,sizeof tr);
len[0] = tot = last = 0;
fa[0] = -1;
}
void ins(int c){
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;~p && tr[p][c] == 0;p = fa[p]) tr[p][c] = u;
if(p==-1) fa[u]=0;
else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
else {
int v = ++tot;
memcpy(tr[v],tr[q],sizeof tr[v]);
fa[v] = fa[q] , len[v] = len[p] + 1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[u] = fa[q] = v;
}
}
void dfs(int u){
mn[0][st[u] = ++tim] = u;
for(int v:G[u]){
dep[v] = dep[u] + len[v] - len[u];
dfs(v);
mn[0][++tim] = u;
}
}
void dfs2(int u){
for(int v:G[u])
dfs2(v),merge(rt[u],rt[u],rt[v]);
}
void BuildR(){
rep(i,1,tot) G[fa[i]].push_back(i);
dfs(0);
rep(j,1,lim-1) rep(i,1,tim-(1<<j)+1)
mn[j][i] = dep[mn[j-1][i]] < dep[mn[j-1][i+(1<<j-1)]] ? mn[j-1][i] : mn[j-1][i+(1<<j-1)];
rep(i,2,tim) lg[i] = lg[i>>1] + 1;
}
void BuildS(){
rep(i,1,tot) G[fa[i]].push_back(i);
dfs2(0);
rep(i,1,tot)
ans += 1ll * (len[i] - len[fa[i]]) * sm[rt[i]];
}
}SA,RA;
int main(){
scanf("%s",S);
n = strlen(S);
rep(i,0,n-1) SA.ins(S[i] - 'a') , SA.pos[i] = SA.last , ans += (i+1) - SA.len[SA.fa[SA.last]],(i == n-2) && (ans *= 2 );
per(i,n-1,0) RA.ins(S[i] - 'a') , RA.pos[i] = RA.last , (i >= 1) && (ans += RA.len[RA.last] - RA.len[RA.fa[RA.last]]);
ans += 2;
RA.BuildR();
rep(i,0,n-3)
ins(rt[SA.pos[i]] , 1 , tim , st[RA.pos[i+2]]);
SA.BuildS();
printf("%lld\n",ans);
}
CF1063F String Journey
將串分解成的形式,其中需要額外滿足是的真子串。
求最大的。
考慮把從小到大也就是從後往前考慮。
發現最優解一定可以是的形式。
那麼之後有兩種做法:
做法一:
考慮到答案是級別的。
那麼我們枚舉答案,然後枚舉所有長度爲答案的子串,看這個字符串刪去第一個或最後一個字符後的字符串有沒有在後面長度爲當前枚舉答案的方案中出現過,如果出現過,那麼這個字符串加在開頭就可以構成長度爲答案的序列。
時間複雜度
簡單哈希即可。
(事實證明這是個垃圾算法,要寫bitset哈希才能卡過(4000ms),可以被,但是隨機數據下可能很難撞。)
#include<bits/stdc++.h>
#define maxn 500005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define S 131
#define LL unsigned long long
using namespace std;
char s[maxn];
LL hs[maxn];
int n;
bitset<maxn>f[2];
#define M 7000007
/*
struct Table{
int info[M],Prev[maxn],tot;
LL val[maxn];
void clear(){
rep(i,1,tot) info[val[i] % M] = 0;
tot = 0;
}
void ins(LL s){
int t = s % M;
val[++tot] = s , Prev[tot] = info[t] , info[t] = tot;
}
int qry(LL s){
for(int i=info[s%M];i;i=Prev[i])
if(s == val[i])
return 1;
return 0;
}
}Map;*/
bitset<M>P;
int main(){//freopen("1.in","r",stdin);
scanf("%d",&n);
scanf("%s",s+1);
reverse(s+1,s+n+1);
int now = 1, pre = 0 , ans = 1 , sm = 1;
rep(i,1,n) f[now][i] = 1;
for(ans=2;;ans++){
swap(now,pre);
f[now].reset();
rep(i,1,n-ans+2) hs[i] = (hs[i] * S + s[i+ans-2]) % M;
P.reset();
rep(i,sm,n-ans+1){
if(P[hs[i]]|| (hs[i+1] != hs[i] && P[hs[i+1]]))
f[now][i+ans-1] = 1;
if(f[pre][i])
P[hs[i-ans+2]] = 1;
}
if(f[now].none()){
printf("%d\n",ans-1);
return 0;
}
sm += ans;
}
}
第二種做法就是不枚舉答案,直接遞推。
設爲前個字符,第個字符被選中的最多段數。
這個轉移有點奇妙:
考慮從大到小枚舉,然後判定存不存在這樣的同時滿足和以及是某個串。
最後一個條件說明在後綴樹是或的一個。
寫子樹查詢即可。
第一個和第二個條件可以被一個性質解決:
對於和,
因爲一定有,這個不等式是顯然的。
所以我們可以得到單調不降,可以直接每次加入這個位置來滿足
所以,我們可以每次讓從開始往下枚舉,總枚舉次數是的。
所以我們就可以在的複雜度內解決此題。
CF906E Reverses
給你兩個長度爲 的字符串 和 。你可以翻轉 的若干個不相交的區間,要求最終 和 相同。問你最少翻轉幾個區間,並輸出方案。
將兩個字符串交替寫下後就轉化爲了求最少偶迴文串劃分。
最小回文分割可以用迴文樹理論優化爲。
#include<bits/stdc++.h>
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
#define maxn 1000005
#define maxc 26
char s[maxn],S[maxn],T[maxn];
int n,f[maxn],g[maxn],pr[maxn];//g the location
int fa[maxn],tr[maxn][maxc],len[maxn],dif[maxn],anc[maxn],tot,last;
void ins(int n,int c){
while(s[n] != s[n-len[last]-1]) last = fa[last];
if(!tr[last][c]){
len[++tot] = len[last] + 2;
int u = fa[last];
while(s[n] != s[n-len[u]-1]) u = fa[u];
fa[tot] = tr[u][c];
dif[tot] = len[tot] - len[fa[tot]];
anc[tot] = (dif[tot] == dif[fa[tot]] ? anc[fa[tot]] : fa[tot]);
tr[last][c] = tot;
}
last = tr[last][c];
}
int main(){
scanf("%s",S);
scanf("%s",T);
n=strlen(S);
rep(i,0,n-1) s[i<<1|1] = S[i] , s[i+1<<1] = T[i];
s[0] = '#';
fa[0] = fa[1] = tot = 1;len[1] = -1;
rep(i,1,n<<1){
ins(i,s[i]-'a');
f[i] = 0x3f3f3f3f;
for(int u=last;u;u=anc[u]){
g[u] = i - len[anc[u]] - dif[u];
if(dif[u] == dif[fa[u]])
if(f[g[fa[u]]] < f[g[u]])
g[u] = g[fa[u]];
if(!(i&1)) if(f[g[u]] + 1 < f[i])
f[i] = f[g[u]] + 1 , pr[i] = g[u];
}
if(s[i] == s[i-1])
if(f[i-2] < f[i])
f[i] = f[i-2] , pr[i] = i-2;
}
if(f[n<<1] >= 0x3f3f3f3f){
puts("-1");
return 0;
}
printf("%d\n",f[n<<1]);
for(int u=n<<1;u;u=pr[u])
if(pr[u]/2+1 != u/2)
printf("%d %d\n",pr[u]/2+1,u/2);
}