[ICPC2018南京站M] 迴文樹+exKMP

題意:

思路:

我們可以通過將s串倒轉過來與t串跑一遍exKMP,然後再遍歷一遍extend數組,在遍歷的過程中將倒過來的s串加入到迴文樹中,當前位置i上的答案實際上就是extend[i]乘上以i開始的迴文串個數,由於我們是倒過來添加的,所以實際上是以i結尾的迴文串個數,這個只需要稍加修改即可實現。

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.io.BufferedReader;
import java.io.InputStreamReader;


public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader sc = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        Task solver = new Task();
        solver.solve(1, sc, out);
        out.close();
    }

    static class Task {
    	//next[i]表示 x[i...m-1]與x[0..m-1]的最長公共前綴
    	//extend[i]表示y[i..n-1]與x[0..m-1]的最長公共前綴
    	
    	void preExKMP(char[] x,int m,int[] next) {
    		next[0]=m;
    		int j=0;
    		while(j+1<m&&x[j]==x[j+1])
    			j++;
    		next[1]=j;
    		int k=1;
    		for(int i=2;i<m;i++) {
    			int p=next[k]+k-1;
    			int L=next[i-k];
    			if(i+L<p+1)
    				next[i]=L;
    			else {
    				j=Math.max(0, p-i+1);
    				while(i+j<m&&x[i+j]==x[j])
    					j++;
    				next[i]=j;
    				k=i;
    			}
    		}
    	}
    	
    	void exKMP(char[] x,int m,char[] y,int n,int[] next,int[] extend) {
    		preExKMP(x,m,next);
    		int j=0;
    		while(j<n&&j<m&&x[j]==y[j])
    			j++;
    		extend[0]=j;
    		int k=0;
    		for(int i=1;i<n;i++) {
    			int p=extend[k]+k-1;
    			int L=next[i-k];
    			if(i+L<p+1)
    				extend[i]=L;
    			else {
    				j=Math.max(0, p-i+1);
    				while(i+j<n&&j<m&&y[i+j]==x[j])
    					j++;
    				extend[i]=j;
    				k=i;
    			}
    		}
    	}
    	
        public void solve(int testNumber, InputReader sc, PrintWriter out) {
            String s=sc.next();
            String t=sc.next();
            StringBuilder temp=new StringBuilder(s);    
            temp.reverse();
            String res=new String(temp);
            int[] next=new int[t.length()+1];
            int[] extend=new int[s.length()+1];
            exKMP(t.toCharArray(),t.length(),res.toCharArray(),res.length(),next,extend);
            boolean[] jud=new boolean[res.length()];
            long ans=0;
            for(int i=0;i<res.length();i++) {
            	if(extend[i]>0) {
            		if(i>0)
            			jud[i-1]=true;
            	}
            }
            PAM pam=new PAM();
            for(int i=0;i<res.length();i++) {
            	int cur=pam.add(res.charAt(i));
            	if(jud[i])
            		ans+=1l*extend[i+1]*cur;         //此處注意次序
            }
            out.println(ans);
            
        }

    }
    
    static class PAM{
    	public static final int MAX=(int) (1e6+5);
    	public static final int N=26;
    	public int[][] next=new int[MAX][N];     //表示編號爲i的節點表示的迴文串在兩邊添加字符c以後變成的迴文串的編號(和字典樹類似)
    	public int[] fail=new int[MAX];   //fail[i]表示節點i失配以後跳轉到長度小於該串且以該節點表示迴文串的最後一個字符結尾的最長迴文串表示的節點 
    	public int[] cnt=new int[MAX];     //表示節點i代表的字符串出現次數  (建樹時求出的不是完全的,最後count()函數跑一遍以後纔是正確的) 
    	public int[] num=new int[MAX];      //num[i]表示以節點i表示的最長迴文串的最右端點爲迴文串結尾的迴文串個數(即本質不同且包括本身)
    	public int[] len=new int[MAX];      //len[i]表示節點i表示的迴文串的長度(一個節點表示一個迴文串)
    	public int[] S=new int[MAX];     //存放添加的字符
    	public int last;        //指向新添加一個字母后所形成的最長迴文串表示的節點
    	public int n;           //表示添加的字符的個數
    	public int p;           //表示添加的節點個數(本質不同的字符串總數)
    	
    	public PAM() {
    		p=0;
    		newNode(0);
    		newNode(-1);
    		last=0;
    		n=0;
    		S[n]=-1;
    		fail[0]=1;
    	}
    	
    	public void init() {
    		p=0;
    		newNode(0);
    		newNode(-1);
    		last=0;
    		n=0;
    		S[n]=-1;
    		fail[0]=1;
    	}
    	
    	public int newNode(int l) {    //新建節點
    		for(int i=0;i<N;i++)
    			next[p][i]=0;
    		cnt[p]=0;
    		num[p]=0;
    		len[p]=l;
    		return p++;
    	}
    	
    	public int getFail(int x) {     //與KMP一樣失配後找一個儘量長的
    		while(S[n-len[x]-1]!=S[n])
    			x=fail[x];
    		return x;
    	}
    	
    	public int add(char x) {
    		int c=x-'a';
    		S[++n]=c;
    		int cur=getFail(last);      //通過上一個迴文串找這個迴文串的位置
    		if(next[cur][c]==0) {       //如果這個迴文串沒有出現過,說明出現了一個新的本質不同的迴文串
    			int now=newNode(len[cur]+2);
    			fail[now]=next[getFail(fail[cur])][c];           //和AC自動機一樣建立fail指針,以便失配後跳轉
    			next[cur][c]=now;
    			num[now]=num[fail[now]]+1;
    		}
    		last=next[cur][c];
    		cnt[last]++;
    		return num[last];
    	}
    	
    	public void count() {
    		for(int i=p-1;i>=0;--i)
    			cnt[fail[i]]+=cnt[i];
    		//父親累加兒子的cnt,因爲如果fail[v]=u,則u一定是v的子迴文串
    	}
    }


    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章