[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());
        }

    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值