字符串
KMP算法,各个书上写的KMP算法的区别:
算法竞赛书上面的next数组的定义是:
n
e
x
t
[
i
]
next[i]
next[i]表示"A中以i结尾的非前缀子串" 与 "A的前缀"能够匹配的最大长度,即:
n
e
x
t
[
i
]
=
m
a
x
{
j
}
next[i] = max\{j\}
next[i]=max{j} 其中
j
<
i
j<i
j<i 并且
A
[
i
−
j
+
1
∼
i
]
=
A
[
1
∼
j
]
A[i - j + 1\sim i] = A[1\sim j]
A[i−j+1∼i]=A[1∼j]
严蔚敏书上的next数组的定义是:
n
e
x
t
[
j
]
=
k
next[j] = k
next[j]=k,则
n
e
x
t
[
j
]
next[j]
next[j]表明当模式中第
j
j
j个字符与主串中相应字符"失配"时,在模式中需重新和主串中该字符进行比较的字符的位置,
n
e
x
t
[
1
]
=
0
next[1] = 0
next[1]=0表示 模式串第0的位置为一个通配符’*’,当串中一个字符next到了next[1]说明模式串没有字符可以与之对齐,那么两者的指针全部都加1
邓俊辉书上的next数组定义与严蔚敏书上的next数组定义是一样的,区别是严蔚敏书上的串是下标是从1开始,严蔚敏书上的串下标为0的位置里面放的是字符串的长度,而邓俊辉书上串的下标是从0开始
POJ 1961 Period
我的想法是 构造字符串的hash表,然后依次比较序列
[
0
,
1
)
[
1
,
2
)
,
.
.
.
,
[
n
−
2
,
n
−
1
)
[0,1) [1,2) ,...,[n-2,n-1)
[0,1)[1,2),...,[n−2,n−1)中每个区间,比较序列
[
0
,
2
)
[
2
,
4
)
[
4
,
6
)
,
.
.
.
,
[
n
−
3
,
n
−
1
)
[0,2) [2,4) [4,6) ,...,[n-3,n-1)
[0,2)[2,4)[4,6),...,[n−3,n−1)…
[
0
,
n
−
1
/
2
)
[
n
−
1
/
2
,
n
−
1
)
[0,n-1/2) [n-1/2,n-1)
[0,n−1/2)[n−1/2,n−1) 时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
AC了 21464K 2266MS
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 1000004;
char data[N];
unsigned long long p[N],weight[N];
unsigned long long Hash(int l,int r){
return p[r] - p[l-1] * weight[r-l+1];
}
int main()
{
int n,cases = 1;
while(true)
{
scanf("%d",&n);
if(n == 0)
break;
scanf("%s",data+1);
map<int,int> m;
p[0] = 0,weight[0] = 1;
for(int i=1;i<=n;i++){//构造Hash表
p[i] = p[i-1] * 131 + data[i];
weight[i] = weight[i-1] * 131;
}
printf("Test case #%d\n",cases);
cases++;
for(int len = 1;len <= n/2;len++){//遍历每种可能的长度
int ind = 1+len,k = 1,flag = 1;
while(flag){
flag = 0;
if(ind + len - 1 <= n && Hash(1,len) == Hash(ind,ind+len-1)){//依次顺着比较同等长度的字符子串
ind = ind + len;
k++;
flag=1;
}
if(k >= 2 && flag)//对于k>1的结果 存入map中,map里面存着对应下标的最大K的值
m[ind-1] = max(m[ind-1],k);
}
}
for(map<int,int>::iterator it = m.begin();it != m.end();it++)
printf("%d %d\n",it->first,it->second);
puts("");
}
return 0;
}
既然这一章的知识点是KMP,那么我就想想怎么用KMP来解这道题,想不出来,康康书上怎么写:
书上是根据一个引理来进行解题的:
S
[
1
∼
i
]
S[1\sim i]
S[1∼i]具有长度为
l
e
n
<
i
len < i
len<i的循环元的充要条件是len能整除i并且
S
[
l
e
n
+
1
∼
i
]
=
S
[
1
∼
i
−
l
e
n
]
S[len + 1 \sim i] = S[1 \sim i - len]
S[len+1∼i]=S[1∼i−len]
5004K 157MS 这快多了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 1000004;
char data[N];
int _next[N];
int main()
{
int n,cases = 1;
while(true){
scanf("%d",&n);
if(n == 0) break;
scanf("%s",data+1);
_next[1] = 0;
for(int i=2,j=0;i <= n;i++){
while(j > 0 && data[i] != data[j+1]) j = _next[j];
if(data[i] == data[j+1]) j++;
_next[i] = j;
}
printf("Test case #%d\n",cases);
cases++;
for(int i=2;i<=n;i++){
if(_next[i] > 0 && i % (i - _next[i]) == 0)
printf("%d %d\n",i,i/(i-_next[i]));
}
puts("");
}
return 0;
}
最小表示法
给定一个字符串
S
[
1
∼
n
]
S[1 \sim n]
S[1∼n],如果我们不断把它的最后一个字符放到开头,最终会得到n个字符串,称这n个字符串是循环同构的。这些字符串中字典序最小的一个,称为字符串S的最小表示
求一个字符串的最小表示
方法是用双指针来遍历字符串,详细见代码:
int n = strlen(s+1);
for(int i=1;i<=n;i++) s[n+i] = s[i];
int i=1,j=2,k;
while(i <= n && j <= n){
for(k = 0;k < n && s[i+k] == s[j+k];k++);
if(k == n) break;
if(s[i+k] > s[j+k]){
i = i + k + 1;
if(i == j) i++;
}else{
j = j + k + 1;
if(i == j) j++;
}
}
一开始的for循环是将字符串复制一遍,这样对于新串s
这里要解释的是 1 为什么
k
=
n
k = n
k=n的时候就break了,当
k
=
n
k = n
k=n时
s
[
m
]
s
[
m
+
1
]
s
[
m
+
2
]
s
[
m
+
3
]
s
[
m
+
4
]
.
.
.
s
[
m
+
n
−
1
]
s[m] s[m+1] s[m+2] s[m+3] s[m+4] ... s[m+n-1]
s[m]s[m+1]s[m+2]s[m+3]s[m+4]...s[m+n−1] 与
s
[
l
]
s
[
l
+
1
]
s
[
l
+
2
]
s
[
l
+
3
]
s
[
l
+
4
]
.
.
.
s
[
n
+
l
−
1
]
s[l] s[l+1] s[l+2] s[l+3] s[l+4] ... s[n+l-1]
s[l]s[l+1]s[l+2]s[l+3]s[l+4]...s[n+l−1]相等 由上题知
s
[
m
]
s
[
m
+
1
]
.
.
s
[
l
−
m
−
1
]
s[m] s[m+1] .. s[l-m-1]
s[m]s[m+1]..s[l−m−1]是一个循环元 而且在这个循环元中 这个串是这个循环元同构串中最小的,因为如果有比这个小的那么在m处的指针应该会跳转。
while里面的操作就是
如果在
i
+
k
i+k
i+k 与
j
+
k
j+k
j+k处发现不相等,假设
s
[
i
+
k
]
>
s
[
j
+
k
]
s[i+k] > s[j+k]
s[i+k]>s[j+k],那么我们当然可以知道从
i
i
i起始的串不是
S
S
S的最小表示,因为存在一个更小的循环同构串从
j
j
j起始的循环同构串,除此之外我们还知道从
i
+
1
i+1
i+1
i
+
2
i+2
i+2 …
i
+
k
i+k
i+k起始的都不是
S
S
S的最小表示 这是因为对于
1
≤
p
≤
k
1 \le p \le k
1≤p≤k,存在一个比从
i
+
p
i+p
i+p起始的循环同构串更小的循环同构串 从
j
+
p
j+p
j+p开始的循环同构串
Trie树
CH1601 前缀统计
感觉这是trie的模板题,用一个数组_end来记录以某个下标为终止的字符串的个数,我的代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 1000004;
int trie[N][26];
int _end[N];
char data[N];
int main()
{
int n,m,tot = 1;
scanf("%d%d",&n,&m);
while(n--){
scanf("%s",data+1);
int len = strlen(data+1),p = 1;
for(int i=1;i<=len;i++){
int ch = data[i] - 'a';//在trie的p位置 插入ch分支
if(trie[p][ch] == 0) trie[p][ch] = ++tot;//如果trie树里面没有的话 就插入相应项tot
p = trie[p][ch];//移动结点到子结点
}
_end[p]++;//以下标为p的子串的个数加1
}
while(m--){
scanf("%s",data+1);
int len = strlen(data+1),p = 1,ans = 0;
for(int i=1;i<=len;i++){
p = trie[p][data[i]-'a'];
if(p == 0) break;//说明后面没有匹配的了,这个时候break就ok了
ans += _end[p];//加上字符串终止在p位置的字符串个数
}
printf("%d\n",ans);
}
return 0;
}
在CH上 这个代码出错
找了半天没有找到错误原因,通过一个一个与光盘进行对照修改 发现错在数组名_end有问题的,改成ed就ok了
CH1602 The XOR Largest Pair
一开始想暴力算法是 将
A
i
A_i
Ai与
A
i
+
1
.
.
.
A
N
A_{i+1} ... A_{N}
Ai+1...AN进行XOR运算,然后将结果的最大值更新mx变量,这样的话 时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
这个题不像上一题 限制了字符串总长度,将
A
i
A_i
Ai看成是长度为31的二进制字符串
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 3200004;
int trie[N][2];
int weight[32];
int main()
{
int n,x,tot = 1,mx = 0;
weight[31] = 1;//因为题目条件中 所有数都是小于2^31 所以这里只取31位二进制位
for(int i=30;i >= 1;i--)
weight[i] = weight[i+1] * 2;//weight[i] 为 2的31-i次方的值,倒着构造的原因是顺着遍历是因为二进制从左到右是权值递减的
scanf("%d",&n);
for(int i=1;i<=n;i++){
int p = 1,w = 1,ans = 0;
scanf("%d",&x);
for(int k=1;k<=31;k++){//将每个输入x看成31位的二进制位
if(x >= weight[k]){//当第k位二进制位为1时,x就大于等于weight[k]
x -= weight[k];//把这个第k位二进制位的1给去掉
if(i > 1 && trie[w][0] != 0) {ans += weight[k];w = trie[w][0];}
else if(i > 1) w = trie[w][1];
if(trie[p][1] == 0) trie[p][1] = ++tot;//构造trie树
p = trie[p][1];
}
else{
if(i > 1 && trie[w][1] != 0) {ans += weight[k];w = trie[w][1];}
else if(i > 1) w = trie[w][0];
if(trie[p][0] == 0) trie[p][0] = ++tot;//构造trie数
p = trie[p][0];
}
}
mx = max(ans,mx);
}
printf("%d\n",mx);
return 0;
}
for循环里面if和else中根据第k位是1还是0进行对称操作,当第i个数输入的时候,在1,…,i-1个数的trie树中进行寻找最大xor值,第i个数的第一个二进制位为b那么就在查看!b分支是否存在, 因为 !b xor b为1,而且顺着找二进制位 二进制位的权值是单调递减的,那么第一个二进制位是最重要的。如果!b不存在那么b分支一定存在 因为trie树不为空,那么就走b分支,然后类似 依次处理后面的二进制位。i从2遍历到n能包含所有可能的情况
POJ 3764 The XOR Longest Path
这个和上一题不一样的是 这里是多个值的xor,因为是树,所以没有环,我就将树的结点x,
p
(
x
)
p(x)
p(x)作为根节点一直到结点x所有边的xor的值,问题是如果边给的不是按照树的结构顺着根往叶子的方向给的话,看了解析,发现考点和我想象的完全不一样,我认为的path是从根结点到树中的某个结点,而题目中要的路径是从任意结点开始,到任意结点结束
忍不住看了一下解析,解析的思路是求出根节点到其它每个结点的xor值,每个结点到根结点所有边的xor值记p(x),那么任意两个结点x和y,x到y所有边上的xor值就为p(x) xor p(y),我的代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 100004;
struct A{
int u,v,w;
}edge[N];//这个存放边的数组
vector<int> g[N];//g[x] 表示顶点为x 的所有邻接边的边号
int visited[N],trie[N][2],weight[32];
int main()
{
int n;
map<int,int> m;
weight[31] = 1;
for(int i=30;i>=1;i--)
weight[i] = weight[i+1] * 2;
while(scanf("%d",&n) == 1){
int root,x,tot = 1,mx = 0;
for(int i=1;i<n;i++){
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
g[edge[i].u].push_back(i);
g[edge[i].v].push_back(i);
}
root = edge[1].u;//随便选一个点作为根结点
m[root] = 0;
queue<int> q;
q.push(root);
while(!q.empty()){//用bfs遍历整个树 然后求得每个顶点到根结点的路径上的xor总和
for(int t = q.size();t>0;t--){
x = q.front();
q.pop();
visited[x] = 1;
for(vector<int>::iterator it = g[x].begin();it != g[x].end();it++){
int nbr = *it,y,u,v;
u = edge[nbr].u, v = edge[nbr].v;
if(x == u) y = v;
else y = u;
if(!visited[y]){
q.push(y);
m[y] = m[x] ^ edge[nbr].w;
}
}
}
}
for(map<int,int>::iterator mit=m.begin();mit!=m.end();mit++){
int ans = 0, val = mit->second,a = 1,p = 1;
for(int k=1;k<=31;k++){//同上一题,边构造trie树边利用trie数求ans
if(val >= weight[k]){//第k位为1的情况
if(mit != m.begin() && trie[a][0] != 0){ans += weight[k];a = trie[a][0];}
else if(mit != m.begin()) a = trie[a][1];
if(trie[p][1] == 0) trie[p][1] = ++tot;
p = trie[p][1];
val -= weight[k];
}
else{//第k位为0的情况
if(mit != m.begin() && trie[a][1] != 0){ans += weight[k];a = trie[a][1];}
else if(mit != m.begin()) a = trie[a][0];
if(trie[p][0] == 0) trie[p][0] = ++tot;
p = trie[p][0];
}
}
mx = max(ans,mx);
}
printf("%d\n",mx);
m.clear();//为了下一次测试 将一些关键结构初始化
for(int i=0;i<n;i++) {g[i].clear();visited[i] = 0;}
for(int k=0;k<=tot;k++) trie[k][0] = trie[k][1] = 0;
}
return 0;
}
示例数据通过了,不知道为什么是wrong answer,查了半天的错 还是没发现,有点郁闷,去康康光盘里的代码,发现这个可以不用map的 用map慢了,因为所有的点的编号是小于n的 所以最后建树的时候可以之间遍历数组就行
还是不知道这个代码为什么错,错误示例又找不到,很烦
我这个代码里面有两个部分 第一个部分是bfs构造数组每个点的映射,第二个部分是根据映射来求最大值
我把第二部分换成光盘里的代码 wrong answer,感觉是我的bfs错了
我再比较比较 发现我的trie数组开小了,应该是N个数 每个数有31个结点,应该选31N 或者 32N
改了以后 发现变成TLE 我想可能是我的vector导致的超时
改成数组操作 就AC了 AC 代码:32320K 860MS
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
using namespace std;
const int N = 100004;
int n, d[N], trie[N*33][2], tot;
int Head[N], Edge[N*2], Leng[N*2], Next[N*2], num,weight[32];
bool v[N];
void dfs(int x) {
for (int i = Head[x]; i; i = Next[i]) {//注意Head 和 Next里面存的都是边的序号,0号边是不存在的,所以0可以用作哨兵
int y = Edge[i], z = Leng[i];
if (v[y]) continue;
v[y] = 1;
d[y] = d[x] ^ z;
dfs(y);
}
}
void add(int x, int y, int z) {
Edge[++tot] = y;
Leng[tot] = z;
Next[tot] = Head[x];
Head[x] = tot;
}
int main()
{
int n;
weight[31] = 1;
for(int i=30;i>=1;i--)
weight[i] = weight[i+1] * 2;
while(scanf("%d",&n) == 1){
int mx = 0;
memset(d, 0, sizeof(d));
memset(trie, 0, sizeof(trie));
memset(v, 0, sizeof(v));
memset(Head, 0, sizeof(Head));
num = 0;
v[0] = 1;//因为有n-1个边说明 0,1,...,n-1个点 都是有的 即点0是存在的
tot = 1;
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
add(u, v, w);//加入u指向v的边 边的权值为w
add(v, u, w);
}
dfs(0);
for(int c=0;c < n;c++){
int ans = 0,a = 1,p = 1;
for(int k=1;k<=31;k++){
if(d[c] >= weight[k]){
if(c > 0 && trie[a][0] != 0){ans += weight[k];a = trie[a][0];}
else if(c > 0) a = trie[a][1];
if(trie[p][1] == 0) trie[p][1] = ++tot;
p = trie[p][1];
d[c] -= weight[k];
}
else{
if(c > 0 && trie[a][1] != 0){ans += weight[k];a = trie[a][1];}
else if(c > 0) a = trie[a][0];
if(trie[p][0] == 0) trie[p][0] = ++tot;
p = trie[p][0];
}
}
mx = max(ans,mx);
}
printf("%d\n",mx);
}
return 0;
}