HDU6599多校第二场 I Love Palindrome String(回文树+马拉车)

I Love Palindrome String

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 818    Accepted Submission(s): 329


Problem Description
You are given a string S=s1s2..s|S| containing only lowercase English letters. For each integer i[1,|S|] , please output how many substrings slsl+1...sr satisfy the following conditions:

  rl+1 equals to i.

  The substring slsl+1...sr is a palindrome string.

  slsl+1...s(l+r)/2 is a palindrome string too.

|S| denotes the length of string S.

A palindrome string is a sequence of characters which reads the same backward as forward, such as madam or racecar or abba
 
Input
There are multiple test cases.

Each case starts with a line containing a string S(1|S|3×105) containing only lowercase English letters.

It is guaranteed that the sum of |S| in all test cases is no larger than 4×106.
 
Output
For each test case, output one line containing |S| integers. Any two adjacent integers are separated by a space.
 
Sample Input
abababa
 
Sample Output
7 0 0 0 3 0 0
 
Source
 
题意:给你一个字符串,然后叫你求,对于每一个长度i-len,问有多少个,回文串的前一半也是回文串。
 
这题的话,我们需要用到一个回文树(也叫回文自动机)来处理,回文树可以在O(nlogm)(其实logm很小,可以忽略,复杂度相当与O(n))的时间里找到所以的回文串,并统计
出每一个回文串的长度
了解回文树的都知道,回文树的没一个结点代表的都是一个回文串,由于我们要判断这个回文串的一半是否为回文串,这么我们可以用hash或manachar处理,我选择的用manachar。
在回文树的结点信息上再添加一个保存在原来字符串位置的区间(l,r),为什么要添加这个呢?因为我们用manachar来判断的回文的话需要用到回文中心,这里我们需要知道的是原回文子字符串str[l~r)]在转换后字符串的回文中心位置(l+r+2),这时我们只需要判断这个回文串的一半是否回文manachar_len[l +(l+r)/2  + 2]的长度是否满足大于等于它的回文长度/2.
回文树不懂的话,代码有注释,可能看着相对容易理解一点(感觉fail数组那不好理解,学了好久好久)
 
这里用hash判断回文的话似乎更简单一些,比如当前选择的结点为ababa我们用hash判断长度为奇数判断hash[l,mid]和hash[mid, r]是否相等就好了
偶长度判断 hash[l,mid]和hash[mid+1, r]
 
  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int MAXN = 300005;
  4 char tmp[MAXN * 2];
  5 int manachar_len[MAXN*2];
  6 struct Palindromic_Tree
  7 {
  8     int next[MAXN][26];
  9     int fail[MAXN];///当前节点的最长回文后缀串的结点(不包括自己),也就是匹配不成功应该跳转的地方,
 10     int cnt[MAXN];
 11     int len[MAXN];///回文串的长度
 12     int num[MAXN];
 13     int s[MAXN];///加入的字符
 14     int p, n, last;///n字符的指针,p表示结点,一个节点代表一个回文串,last指向新添加一个字母后所形成的最长回文串表示的节点
 15     struct node
 16     {
 17         int l, r;
 18     } P[MAXN];
 19     int newNode(int length)
 20     {
 21         for (int i = 0; i < 26; i++)
 22             next[p][i] = 0;
 23         num[p] = cnt[p] = 0;///记录信息
 24         len[p] = length;
 25         P[p].l = 0;
 26         P[p].r = 0;
 27         return p++;
 28     }
 29     void init()
 30     {
 31         p = 0;
 32         newNode(0);//0 号节点代表偶数根,长度为0 , 1号节点代表奇根,长度为 -1
 33         newNode(-1);
 34         last = n = 0;///刚开始时,所有节点指向0号节点
 35         fail[0] = 1;///0号节点的fail 指针指向  1(奇根) 号节点
 36         s[0] = -1;///开头放一个字符集中没有的字符,减少特判
 37     }
 38     int get_fail(int x)//找到第一个使得S[n - len[last] - 1] == S[n]的last
 39     {
 40         while(s[n - len[x] - 1] != s[n])
 41             x = fail[x];
 42         return x;
 43     }
 44     void add(int c)
 45     {
 46         int x = c-'a';
 47         s[++ n] = x;
 48         int cur = get_fail(last);///cur节点相当于now节点的父亲结点 cur->now
 49         if(!next[cur][x])
 50         {
 51             int now = newNode(len[cur] + 2);
 52             fail[now] = next[ get_fail( fail[cur] ) ][ x ];///fail[cur]得到cur的最长回文后缀,
 53             ///再通过这个回文后缀找到使得S[n - len[last] - 1] == S[n]的last结点
 54             ///比如abbaabba  cur指向的结点为bbaabb,这个节点的最长回文后缀为bb,那么看bb['a'[bb]'x']
 55             ///看'x'是否和'a'相等,如果相等,那么fail指正指向['a'[bb]'x']这个节点,否则的话就不断去缩小bb
 56             ///回文后缀,直到到达 1 号节点,fail 指向长度为 1 的 'x' 节点;
 57             next[cur][x] = now;
 58             num[now] = num[fail[now]] + 1;
 59         }
 60         last = next[cur][x];
 61         P[last].l = n -1 - len[last] + 1;
 62         P[last].r = n - 1;
 63         cnt[last] ++;
 64     }
 65     void count()
 66     {
 67         for(int i = p - 1; i >= 0; i --)
 68         {
 69             /// printf("%d
", cnt[i]);
 70             cnt[fail[i]] += cnt[i];
 71         }
 72     }
 73 } AC;
 74 
 75 int init(char st[])
 76 {
 77     int i,str_len=strlen(st);
 78     tmp[0]='@';///加一个特殊字符,防止越界
 79     for(int i = 1; i <= 2*str_len ; i+=2)
 80     {
 81         tmp[i]='#';
 82         tmp[i+1]=st[i/2];
 83     }
 84     tmp[2*str_len+1]='#';
 85     tmp[2*str_len+2]='$';///字符串结尾加一个字符,防止越界
 86     return 2*str_len+1;///返回转换字符串的长度
 87 }
 88 
 89 void manachar(char st[],int str_len)
 90 {
 91     int p_mx=0,ans=0,po=0;///p_mx即为“当前”计算回文串最右边字符的最大值,po为当前该回文字符串的中心
 92     for(int i=1; i<=str_len; i++)
 93     {
 94         if(i<p_mx)
 95             manachar_len[i]=min(p_mx-i,manachar_len[2*po-i]);///po对应最大回文子串中心点的位置
 96         else
 97             manachar_len[i]=1;
 98         while(st[i-manachar_len[i]]==st[i+manachar_len[i]])
 99             manachar_len[i]++;
100         if(manachar_len[i]+i>p_mx)
101         {
102             po=i;
103             p_mx=i+manachar_len[i];
104         }
105         ans=max(ans,manachar_len[i]);
106     }
107    // return ans-1;///返回manachar_len[i]中的最大值减一即为原串的最长回文子串的长度
108 }
109 char str[MAXN];
110 int len_ans[MAXN];
111 int main()
112 {
113     while(~scanf("%s",str))
114     { 
115         int len_tmp = init(str);
116         manachar(tmp, len_tmp);
117         memset(len_ans, 0,sizeof(len_ans));
118         int len = strlen(str);
119         AC.init();
120         for(int i = 0; i < len; i++)
121         {
122             AC.add(str[i]);
123         }
124         AC.count();
125         long long ans = 1;
126         for(int i = 0; i < AC.p; i++)
127         {
128             if(manachar_len[AC.P[i].l+(AC.P[i].l+AC.P[i].r)/2+2] - 1 >= AC.len[i]/2)
129             {
130                 len_ans[AC.len[i]] += AC.cnt[i];
131             }
132         }
133         for(int i = 1; i <= len; i++)
134         {
135             if(i == 1 )
136             printf("%d",len_ans[i]);
137             else
138               printf(" %d",len_ans[i]);
139         }
140         puts("");
141     }
142 
143     return 0;
144 }
 
原文地址:https://www.cnblogs.com/yuanlinghao/p/11268877.html