1. 程式人生 > 其它 ># 7.21 學習筆記

# 7.21 學習筆記

1 字串hash

1.1 Codeforces 955D Scissors

我們設 \(L_i\) 表示在第二個字串(以下稱該字串為 \(t\) )長度為 \(i\) 的字首在第一個字串(以下稱該字串為 \(s\) )出現最高左的位置是哪個位置。不難發現,去掉無意義的位置,\(L\) 這個陣列一定是遞增的,所以我們可以考慮用雙指標來完成這個事情。

同樣,我們可以預處理出 \(R_i\) 表示長度為 \(i\) 的字尾在 \(s\) 中出現最靠右的位置。

這樣,\(t\) 被分成兩半的情況就分別討論完了,需要注意的是還有一種情況,就是你這個 \(t\) 可能存在於 \(s\) 非常靠左的位置或非常靠右的位置,且 \(s\)

的長度要小於 \(t\) ,在這種情況下,上面是討論不到的。所以我們需要額外討論一下 \(t\)\(s\) 中出現的情況。這種情況我們直接取字首的一段和字尾的一段就可以了。

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 500010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const ull mod=13331;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}
template<typename T> inline T Max(T a,T b){
    return a<b?b:a;
}

template<typename T> inline T Min(T a,T b){
    return a<b?a:b;
}

int n,m,L[N],R[N],k;
char s[N],t[N];
ull hs[N],ht[N],mpow[N];

inline ull GetHash(ull *h,int l,int r){
    return h[r]-h[l-1]*mpow[r-l+1];
}

inline void prework(){
    int l,r;l=1;r=1;
    int minn=Min(m,k);
    while(r-l+1<=minn&&r<=n-k){
        ull ha=GetHash(hs,l,r);
        ull hb=GetHash(ht,1,r-l+1);
        if(ha==hb){
            L[r-l+1]=r;
            if(l>1) l--;
            else r++;
        }
        else{l++;r++;}
    }
    for(int i=1;i<=m;i++) if(L[i]<k) L[i]=0;
    l=r=k;
    while(r-l+1<=minn&&r<=n-k&&L[r-l+1]==0){
        ull ha=GetHash(hs,l,r);
        ull hb=GetHash(ht,1,r-l+1);
        if(ha==hb){
            L[r-l+1]=r;
            if(l>1) l--;
            else r++;
        }
        else{l++;r++;}
    }
    l=r=n;
    while(r-l+1<=minn&&l>=k+1){
        ull ha=GetHash(hs,l,r);
        ull hb=GetHash(ht,m-r+l,m);
        if(ha==hb){
            R[r-l+1]=l;
            if(r<n) r++;
            else l--;
        }
        else{l--;r--;}
    }
    for(int i=1;i<=m;i++) if(R[i]>n-k+1) R[i]=0;
    l=r=n-k+1;
    while(r-l+1<=minn&&l>=k+1&&R[r-l+1]==0){
        ull ha=GetHash(hs,l,r);
        ull hb=GetHash(ht,m-r+l,m);
        if(ha==hb){
            R[r-l+1]=l;
            if(r<n) r++;
            else l--;
        }
        else{l--;r--;}
    }
    for(int i=1;i<=m;i++){
        if(!L[i]) L[i]=-INF;
        if(!R[i]) R[i]=INF;
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(m);read(k);
    int maxx=Max(n,m);
    mpow[0]=1;for(int i=1;i<=maxx;i++) mpow[i]=mpow[i-1]*mod;
    scanf("%s%s",s+1,t+1);
    for(int i=1;i<=n;i++) hs[i]=hs[i-1]*mod+s[i]-'a';
    for(int i=1;i<=m;i++) ht[i]=ht[i-1]*mod+t[i]-'a';
    prework();
    // printf("L: ");for(int i=1;i<=m;i++) printf("%d ",L[i]);putchar('\n');
    // printf("R: ");for(int i=1;i<=m;i++) printf("%d ",R[i]);putchar('\n'); 
    for(int i=1;i<=m;i++){
        if(L[i]==-INF||R[m-i]==INF) continue;
        else if(L[i]<R[m-i]){
            printf("Yes\n");
            printf("%d %d\n",L[i]-k+1,R[m-i]);
            return 0;
        }
    }
    ull all=GetHash(ht,1,m);
    for(int i=1;i<=n-m+1;i++){
        ull now=GetHash(hs,i,i+m-1);
        if(all==now){
            printf("Yes\n");
            printf("1 %d\n",n-k+1);
            return 0;
        }
    }
    printf("No\n");
    return 0;
}
/*
不能夠從兩邊避免 k ,但是需要防止 k 的情況。
51 13 11
cbcbbcbbbbbcccbcccbbbcbbbbbbbbbbbbcbbbcbbcbbbbcbbbb
bbbbbccbcbbcb
ans:
Yes
3 38
*/

1.2 Codeforces 985F Isomorphic Strings

這個題我們只需要改變 hash 的方式,把每一個字元單獨拿出來,用出現次數來做 hash,這樣就可以完成這道題了。同時我們可以利用 multiset 來判斷相等,這樣可以避免 hash 出錯。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 300000
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int base=114514;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,m,mpow[N],h[26][N];
char s[N];

inline int GetHash(int *h,int l,int r){
    // printf("l:%d r:%d r-l+1:%d\n",l,r,r-l+1);
    return ((h[r]-h[l-1]*mpow[r-l+1]%mod)+mod)%mod;
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(m);scanf("%s",s+1);
    mpow[0]=1;for(int i=1;i<N;i++) mpow[i]=(mpow[i-1]*base)%mod;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=25;j++) h[j][i]=(h[j][i-1]*base+1+(s[i]-'a'==j))%mod;
    }
    for(int i=1;i<=m;i++){
        int a,b,c;read(a);read(b);read(c);
        multiset<int> s,t;
        for(int j=0;j<=25;j++){
            // printf("%lld %lld\n",GetHash(h[j],a,a+c-1),GetHash(h[j],b,b+c-1));
            s.insert(GetHash(h[j],a,a+c-1));
            t.insert(GetHash(h[j],b,b+c-1));
        }
        printf("%s\n",s==t?"YES":"NO");
    }
}//

1.3 二維hash darkbzoj2351

二維 hash 其實和一維 hash 一樣,也就是說我們拿第一個質數橫著做一遍 hash,然後換一個質數,把 hash 值當成要做 hash 的值,豎著在做一遍 hash,這樣就可以做了。

程式碼:

// #include<bits/stdc++.h>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<map>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 201
#define M 2010
using namespace std;

const int INF=0x3f3f3f3f;
const ull mod=131;
const ull base=13331;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){
    return a<b?b:a;
}

ull mpow[M],bpow[M];

ull d[M][M],f[M][M];
// set<ull> S;
map<ull,int> S;

int m,n,a,b,q,ans[N*10];
char s[M];

int main(){


    read(n);read(m);read(a);read(b);int maxx=Max(n,m);
    mpow[0]=bpow[0]=1;
    for(int i=1;i<=maxx;i++){
        mpow[i]=mpow[i-1]*mod;bpow[i]=bpow[i-1]*base;
    }
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=1;j<=m;j++){
            d[i][j]=d[i][j-1]*mod+s[j]-'0';
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            d[i][j]+=d[i-1][j]*base;
    for(int i=1;i<=n-a+1;i++)
        for(int j=1;j<=m-b+1;j++){
            int x=i+a-1,y=j+b-1;
            ull now=d[x][y]-d[x][j-1]*mpow[y-j+1];
            ull now2=d[i-1][y]-d[i-1][j-1]*mpow[y-j+1];
            ull nowans=now-now2*bpow[x-i+1];
            // if(S.count(nowans)) ans[S[nowans]]=1;
            S[nowans]=1;
        }
    read(q);
    for(int i=1;i<=q;i++){
        ull nowans=0;
        for(int j=1;j<=a;j++){
            ull now=0;
            scanf("%s",s+1);
            for(int k=1;k<=b;k++){
                now*=mod;now+=s[k]-'0';
            }
            nowans*=base;nowans+=now;
        }
        // printf("i:%d nowans:%llu\n",i,nowans);
        // S[nowans]=i;
        if(S.count(nowans)) ans[i]=1;
    }
    for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
}

1.4 UVA1401 Remember the Word

顯然是一個 dp 。我們設 \(f_i\) 表示考慮完前 \(i\) 個字元的方案數,那麼轉移就是:

\[f_i=\sum\limits_{s_{i+1,j}\in dictionary}f_j+1 \]

我們發現這個東西的複雜度在於判斷是否在字典裡,所以我們不妨反過來做:

\(f_i\) 表示考慮完 \(s_{i,len}\) 的方案數,轉移和上面差不多,至於判斷是否在字典裡,我們可以建一棵 Trie 樹來幫助我們判斷,因為 Trie 樹的節點深度不會超過 \(100\) ,所以複雜度為 \(O(100n)\)

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 400010
#define M 110
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=20071027;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int ch[N][26],tot,End[N],n,f[N];
char s[N],t[M];

inline void Insert(char *s){
    int len=strlen(s+1),p=0;
    for(int i=1;i<=len;i++){
        int c=s[i]-'a';
        if(!ch[p][c]) ch[p][c]=++tot;
        p=ch[p][c];
    }
    End[p]=1;
}



int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    int test=0;
    while(scanf("%s",s+1)!=EOF){
        test++;
        memset(ch,0,sizeof(ch));
        memset(End,0,sizeof(End));
        read(n);
        for(int i=1;i<=n;i++){
            scanf("%s",t+1);Insert(t);
        }
        memset(f,0,sizeof(f));
        int len=strlen(s+1);f[len+1]=1;
        for(int i=len;i>=1;i--){
            int p=0;
            for(int j=i;j<=len;j++){
                int c=s[j]-'a';
                if(!ch[p][c]) break;
                p=ch[p][c];if(End[p]) (f[i]+=f[j+1])%=mod;
            }
        }
        printf("Case %d: ",test);
        printf("%d\n",f[1]);
    }
    return 0;
}

1.5 UVA1519 Dictionary Size

看到字首和字尾我們首先想到 Trie 樹。我們正向建一棵 Trie 樹,反向建一棵 Trie 樹。那麼答案我們首先認為是兩顆 Trie 樹的節點個數減 \(1\) 的乘積。

不難發現,有一些字串我們沒有統計,就是那些長度為 \(1\) 的字串,這是因為我們上面的操作只統計了長度大於等於 \(2\) 的字串。與此同時,我們有一些字串被重複統計,不難發現,重複統計的次數是每一個字元在 Trie 樹上的出現次數之積,減去就可以了。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 400010
#define M 50
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

char s[M];
int n,ans,c[M];

struct Trie{
    int p[N][26],tot;
    int cnt[M];
    inline Trie(){tot=0;}
    inline void Insert(char *s){
        int now=0;int len=strlen(s);
        for(int i=0;i<len;i++){
            int k=s[i]-'a';
            if(!p[now][k]){
                p[now][k]=++tot;
                if(i){
                    cnt[k]++;
                }
            }
            now=p[now][k];
        }
    }
    inline void clear(){
        tot=0;memset(p,0,sizeof(p));
        memset(cnt,0,sizeof(cnt));
    }
};
Trie t1,t2;

signed main(){
    while(cin>>n){
        for (int i = 0; i < n; i++) {
            scanf("%s", s);
 
            int n = strlen(s);
            if (n == 1)
                c[s[0] - 'a'] = 1;
            t1.Insert(s);
            reverse(s, s + n);
            t2.Insert(s);
        }
        ans=t1.tot*t2.tot;
        for(int i=0;i<=25;i++){
            if(c[i]) ans++;
            ans-=(t1.cnt[i]*t2.cnt[i]);
        }
        printf("%lld\n",ans);
        t1.clear();t2.clear();
        ans=0;memset(c,0,sizeof(c));
    }
}

2 AC自動機

2.1 P4052 [JSOI2007]文字生成器

首先關注到不出現,不出現的話考慮用 AC 自動機,主要是藉助 Trie 樹這個結構。我們簡單的取一下補集。

既然不能出現,那麼也就是說不能做到結束節點,更進一步,我們在 Trie 樹上走,我們也不能走到一個節點,其所表示的字串的字尾是這 \(n\) 個字串中的一個。換句話說,我們要把所有滿足字尾不合法的節點標記一下,怎麼標記?我們在建立 AC 自動機的時候把這個節點的 end 與其後綴連結的 end 或一下就可以了。

然後就比較套路了,我們設 \(f_{i,j}\) 表示走了 \(i\) 步,在 Trie 圖上的 \(j\) 節點,方案數。轉移就列舉下一步要去哪裡即可。然後把每個位置上走了 \(m\) 步的方案數累加就可以了。

值得一提的是,Trie 圖並不是 DAG ,不用擔心在 Trie 圖上做 dp 的正確性。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 110
#define M 6100
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e4+7;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct node{
    int ch[26],end,fail;
};

int f[N][M],n,m;
char s[N];

struct AC{
    node p[M];int tot;
	inline AC(){memset(p,0,sizeof(p));tot=0;}
    inline void Insert(char *s){
        int len=strlen(s),now=0;
        for(int i=0;i<=len-1;i++){
            int k=s[i]-'A';
            if(!p[now].ch[k]) p[now].ch[k]=++tot;
            now=p[now].ch[k];
        }
        p[now].end=1;
    }
    inline void GetFail(){
        queue<int> q;
		while(q.size()) q.pop();
        for(int i=0;i<=25;i++) if(p[0].ch[i]) q.push(p[0].ch[i]);
        while(q.size()){
            int top=q.front();q.pop();
            for(int i=0;i<=25;i++){
                if(p[top].ch[i]){
                    p[p[top].ch[i]].fail=p[p[top].fail].ch[i];
                    p[p[top].ch[i]].end|=p[p[p[top].fail].ch[i]].end;
                    q.push(p[top].ch[i]);
                }
                else p[top].ch[i]=p[p[top].fail].ch[i];
            }
        }
    }
    inline void DP(){
        f[0][0]=1;
        for(int i=1;i<=m;i++){
            for(int j=0;j<=tot;j++){
                for(int k=0;k<=25;k++){
                    if(p[p[j].ch[k]].end==1) continue;
                    (f[i][p[j].ch[k]]+=f[i-1][j])%=mod;
                }
            }
        }
    }
    inline int GetAns(){
        int ans=0;
        for(int i=0;i<=tot;i++){
            ans+=f[m][i];ans%=mod;
        }
        return ans;
    }
};
AC ac;

inline int ksm(int a,int b,int mod){
	int res=1;
	while(b){
		if(b&1) (res*=a)%=mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

int main(){
	// freopen("my.in","r",stdin);
	// freopen("my.out","w",stdout);
	ios::sync_with_stdio(false);cin.tie(0);
	// read(n);read(m);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>s;
		ac.Insert(s);
	}
	ac.GetFail();ac.DP();
	int ans=ac.GetAns();
	// printf("%d\n",((ksm(26,m,mod)-ans)%mod+mod)%mod);
	cout<<((ksm(26,m,mod)-ans)%mod+mod)%mod<<"\n";
	return 0;
}
/*
update 1:
row 48 "p[p[top].ch[i]].end|=p[p[top].fail].ch[i];"
update 2:
row 101 -> row 100
update 3
row 93 -> 92
*/

2.2 P4600 [HEOI2012]旅行問題

建立 AC 自動機,在 fail 樹上求 LCA 就可以了。

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 3000010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const ll mod=1e9+7;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

vector<int> ID[N];

struct node{
    int ch[26],fail,end;
    ll code;
};

struct edge{
    int to,next;
    inline void intt(int to_,int ne_){
        to=to_;next=ne_;
    }
};
edge li[N];
int head[N],tail;

inline void add(int from,int to){
    li[++tail].intt(to,head[from]);
    head[from]=tail;
}

struct AC{
    node p[N];int size;
    inline AC(){size=1;}
    inline void Insert(int id,char *s){
        int len=strlen(s);int now=1;
        for(int i=0;i<=len-1;i++){
            int k=s[i]-'a';
            if(!p[now].ch[k]) p[now].ch[k]=++size;
            int last=now;now=p[now].ch[k];
            p[now].code=(p[last].code*26%mod+k)%mod;
            ID[id].push_back(now);
        }
        p[now].end++;
    }
    inline void GetFail(){
        queue<int> q;
        for(int i=0;i<=25;i++){
            if(p[1].ch[i]){q.push(p[1].ch[i]);p[p[1].ch[i]].fail=1;}
            else p[1].ch[i]=1;
        }
        while(q.size()){
            int top=q.front();q.pop();
            for(int i=0;i<=25;i++){
                if(p[top].ch[i]){
                    p[p[top].ch[i]].fail=p[p[top].fail].ch[i];
                    q.push(p[top].ch[i]);
                }
                else p[top].ch[i]=p[p[top].fail].ch[i];
            }
        }
    }
    inline void BuildFailTree(){
        for(int i=2;i<=size;i++){
            // printf("i:%d fail:%d\n",i,p[i].fail);
            add(p[i].fail,i);
        }
    }
};
AC ac;

int n,q;
char s[N];

int top[N],son[N],siz[N],deep[N],fa[N];

inline void dfs1(int k,int fat){
    deep[k]=deep[fat]+1;siz[k]=1;fa[k]=fat;
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(to==fat) continue;
        dfs1(to,k);
        siz[k]+=siz[to];
        if(siz[son[k]]<siz[to]) son[k]=to;
    }
}

inline void dfs2(int k,int t){
    top[k]=t;
    if(son[k]) dfs2(son[k],t);
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(to==fa[k]||to==son[k]) continue;
        dfs2(to,to);
    }
}

inline int GetLca(int a,int b){
    while(top[a]!=top[b]){
        if(deep[top[a]]<deep[top[b]]) swap(a,b);
        a=fa[top[a]];
    }
    if(deep[a]>deep[b]) swap(a,b);
    return a;
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++){
        scanf("%s",s);ac.Insert(i,s);
    }
    ac.GetFail();ac.BuildFailTree();
    dfs1(1,0);dfs2(1,1);
    read(q);
    for(int i=1;i<=q;i++){
        int a,b,c,d;read(a);read(b);read(c);read(d);
        int p1=ID[a][b-1],p2=ID[c][d-1];
        // printf("p1:%d p2:%d\n",p1,p2);
        // printf("lca:%d\n",GetLca(p1,p2));
        printf("%lld\n",ac.p[GetLca(p1,p2)].code);
    }
    return 0;
}

3 KMP 演算法

KMP 演算法不會考匹配,而會考對 next 陣列的理解和運用。

3.1 HDU3336

\(f_i\) 表示以 \(i\) 結尾的所有的字首的出現次數,那麼顯而易見的轉移是 \(f_i=f_{next_i}+1\) ,之所以加 \(1\) 是整個長度為 \(i\) 的字串,所有以 \(next_i\) 結尾的字首都會在以 \(i\) 結尾中出現 \(1\) 次。

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=10007;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int t,n,nxt[N],f[N],ans;
char s[N];

int main(){
    read(t);
    while(t--){
        read(n);
        scanf("%s",s+1);
        nxt[1]=0;
        for(int i=2,j=0;i<=n;i++){
            while(j>0&&s[j+1]!=s[i]) j=nxt[j];
            if(s[j+1]==s[i]) j++;
            nxt[i]=j;
        }
        for(int i=1;i<=n;i++){
            f[i]=f[nxt[i]]+1;f[i]%=mod;
            ans+=f[i];ans%=mod;
        }
        printf("%d\n",ans);
        for(int i=1;i<=n;i++) nxt[i]=f[i]=0;ans=0;
    }
}

3.2 POJ 2752

KMP 的題我用字串 hash 做的,就這樣吧。。。

程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 400010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const ull mod=13331;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

ull a,b;
char s[N];

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    while(cin>>(s+1)){
        a=b=0;
        ull ji=1;int n=strlen(s+1);
        for(int i=1;i<=n;i++){
            a*=mod;a+=s[i]-'a';
            int j=n-i+1;int siz=n-j;
            b+=ji*(s[j]-'a');ji*=mod;
            if(a==b) printf("%d ",i);
        }
        putchar('\n');
    }
}