HASH
雪花雪花雪花
这个就是因为需要知道是否存在两个相同的,并且它们实际上是以环状存在,并且有正反两面,所以每个雪花,先求出它正反两面的最小表示法,然后对这个最小表示法hash存入数组,对数组进行排序,查看是否有相同的元素,若有则存在。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int snows[N][6], idx[N];//snows储存所有的最小表示法,idx储存每个表示法的序号。
void get_min(int *b)//求最小表示法
{
static int a[12];
for (int i = 0; i < 12; i ++ ) a[i] = b[i % 6];
int i = 0, j = 1, k;
while (i < 6 && j < 6)
{
for (k = 0; k < 6 && a[i + k] == a[j + k]; k ++ );
if (k == 6) break;
if (a[i + k] > a[j + k])
{
i += k + 1;
if (i == j) i ++ ;
}
else
{
j += k + 1;
if (i == j) j ++ ;
}
}
k = min(i, j);
for (i = 0; i < 6; i ++ ) b[i] = a[i + k];
}
bool cmp(int a, int b)//排序的比较函数
{
for (int i = 0; i < 6; i ++ )
if (snows[a][i] < snows[b][i])
return true;
else if (snows[a][i] > snows[b][i])
return false;
return false;
}
bool cmp2(int a[], int b[])//比较最小表示法是否相同
{
for (int i = 0; i < 6; i ++ )
if (a[i] < b[i])
return true;
else if (a[i] > b[i])
return false;
return false;
}
int main()
{
scanf("%d", &n);
int snow[6], isnow[6];
for (int i = 0; i < n; i ++ )
{
for (int j = 0, k = 5; j < 6; j ++, k -- )
{
scanf("%d", &snow[j]);
isnow[k] = snow[j];
}
get_min(snow);
get_min(isnow);
if (cmp2(snow, isnow)) memcpy(snows[i], snow, sizeof snow);
else memcpy(snows[i], isnow, sizeof isnow);
idx[i] = i;
}
sort(idx, idx + n, cmp);
for (int i = 1; i < n; i ++ )
{
if (!cmp(idx[i], idx[i - 1]) && !cmp(idx[i - 1], idx[i]))
{
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
return 0;
}
兔子与兔子
首先对序列的每一个前缀进行hash,然后当提问每一个区间时,
求出这个区间的hash值,然后比较是否相同。
字符串hash的方法:首先将每一个字符转化成一个数字,然后hash。
计算区间
l
,
r
l,r
l,r的hash值:
h
[
r
]
−
h
[
l
−
1
]
∗
p
(
r
−
l
+
1
)
,
(
p
可
以
为
131
或
者
13331
,
因
为
这
两
个
数
h
a
s
h
起
来
重
复
的
比
较
少
)
h[r] - h[l - 1] * p^{(r - l + 1)},(p可以为131或者13331,因为这两个数hash起来重复的比较少)
h[r]−h[l−1]∗p(r−l+1),(p可以为131或者13331,因为这两个数hash起来重复的比较少)
这两个模板可以记一下。
#include<bits/stdc++.h>
using namespace std;
const int N = 1000009;
typedef unsigned long long ULL;
ULL s[N];
ULL p[N];
char a[N];
ULL change(int l,int r)
{
return s[r] - s[l - 1] * p[r - l + 1];
}
int main()
{
// string a;
cin >> a;
p[0] = 1;
for(int i = 0;i < sizeof(a);i ++)
s[i + 1] = a[i] - 'a' + 1;
for(int i = 1;i <= sizeof(a);i ++)
{
s[i] += s[i - 1] * 131;
p[i] = p[i - 1] * 131;
}
// for(int i = 1;i <= a.length();i ++)
// cout<<s[i];
// cout << endl;
int n;
cin >> n;
for(int i = 1;i <= n;i ++)
{
int l1,l2,r1,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
// cout<<change(l1,r1)<<" "<<change(l2,r2);
if(change(l1,r1) == change(l2,r2))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
回文子串的最大长度
方法一
这个首先依次枚举每个点,然后二分左右两边的长度计算hash是否相等(在这之前要先把整个字符串从左往右以及从右往左的前缀hash值求出来)。
处理字符串的技巧:
在每两个字符之间加上#,这样就不用处理是奇数还是偶数了,但是最后回文子串长度算出来之后需要处理掉其中的#的长度(但是因为预处理,不管在中心点是字符还是#,最后的边界一定是#,这样稍微推一下就知道l的长度就是回文子串的长度)。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 1000100;
ULL que[2*N],rque[2*N],p[2*N];
ULL get(ULL h[],int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int T = 0;
string s;
while(cin >> s && s != "END")
{
int len = s.size();
for(int i = 1;i <= len * 2 + 1;i ++)
{
if(i % 2 == 1)
que[i] = 27,rque[len * 2 + 2 - i] = 27;
else
que[i] = s[i / 2 - 1] - 'a' + 1,rque[len * 2 + 2 - i] = que[i];
}//处理字符串
len =len * 2 + 1;
p[0] = 1;
for(int i = 1;i <= len;i ++)
{
que[i] += que[i - 1] * 131;
rque[i] += rque[i - 1] * 131;
p[i] = p[i - 1] * 131;
}//计算hash值
int ans = 0;
for(int i = 1 ;i <= len;i ++)//枚举点
{
int l = 0, r = min(i - 1, len - i);//注意边界问题,长度不能超过到边界的值
while (l < r)
{
int mid = l + r + 1 >> 1;
if (get(que, i - mid, i - 1) != get(rque, len - (i + mid) + 1, len - (i + 1) + 1)) r = mid - 1;//i位置的点,倒过来对应的是n - i + 1的位置
else l = mid;
}
ans = max(ans,l);
}
printf("Case %d: %d\n",++ T,ans);
}
return 0;
}
方法二
manacher算法:复杂度
O
(
n
)
O(n)
O(n)
manacher算法详解
后缀数组
后缀数组其实就是求
j
到
n
j到n
j到n的区间hash值
首先还是先求整个字符串的hash值,然后排序,排序的时候记得储存序号,排序的时候先求公共前缀(二分长度),然后判断公共前缀的下一位数的大小。
#include<bits/stdc++.h>
using namespace std;
const int N = 300009;
typedef unsigned long long ULL;
ULL h[N],p[N];
int id[N];//储存序号
char s[N];
int len;
ULL get(int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int get_min_common(int a,int b)//a,b代表从a到n的后缀,和从b到n的后缀
{
int l = 0,r = min(len - a + 1,len - b + 1);//二分的长度不能超过它们到n的长度。
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(get(a,a + mid - 1) == get(b,b + mid - 1)) l = mid;
else r = mid - 1;
}
return l;
}
bool cmp(int a,int b)
{
int n = get_min_common(a,b);
int va = a + n > len ? INT_MIN:s[a + n];//如果公共前缀和的长度超过了len ,那么就直接变为极小值
int vb = b + n > len ? INT_MIN:s[b + n];
return va < vb;
}
int main()
{
scanf("%s",s + 1);
len = strlen(s + 1);
p[0] = 1;
for(int i = 1;i <= len;i ++)
{
h[i] = h[i - 1] * 131 + s[i] - 'a' + 1;
p[i] = p[i - 1] *131;
id[i] = i;
}
sort(id + 1,id + len + 1,cmp);
for(int i = 1;i <= len;i ++)
printf("%d ",id[i] - 1);
cout<<endl;
cout<<0<<" ";
for(int i = 2;i <= len;i ++)
{
printf("%d ",get_min_common(id[i],id[i - 1]));//求相邻两个的最大公共前缀
}
return 0;
}
字符串
周期
这个题要利用KMP算法的性质(详见算法竞赛);
#include<bits/stdc++.h>
using namespace std;
const int N = 1000009;
int n;
int Next[N];
char s[N];
void get_next()
{
for(int i = 2,j = 0;i <= n;i ++)
{
while(j && s[i] != s[j + 1]) j = Next[j];
if(s[i] == s[j + 1]) j ++;
Next[i] = j;
}
}
int main()
{
int T=0;
while(cin >> n && n)
{
cin>>(s + 1);
get_next();
printf("Test case #%d\n",++ T);
for(int i = 2;i <= n; i ++)
{
int x = i - Next[i];
if(x != i && i % x == 0) printf("%d %d\n",i,i / x);
}
printf("\n");
}
}
然后本章比较重要的就是KMP算法还有最小表示法了(下面附上一篇不错的文章(模板必须要背))
KMP算法和其他字符串匹配算法
说实话感觉sunday要好理解一点
KMP算法的应用(主要是周期):
最短周期:串长-最长相同前后缀长
最长周期:串长-最短相同前后缀长
(求最长周期,所以要知道最短相同前后缀长。考虑对于每一个串它的最短后缀长一定包含在最长后缀长之中:所以不断的找next,直到找到最短的非零的那个就好啦。这里运用一个技巧:从头往后枚举,next[i]直接就等于next[next[i]](如果next[next[i]]不等于0的话)
Trie
前缀统计
一个trie的简单应用,把原串的放进来,然后输入字符串匹配就可以了。
模板还是必须背住
#include<bits/stdc++.h>
using namespace std;
const int N = 1000009,M = 500000;
int n,m;
int son[M][26],cnt[N],idx;
char str[N];
void insert()
{
int p = 0;
for(int i = 0;str[i];i ++)
{
int &s = son[p][str[i] - 'a'];
if(!s)
s = ++ idx;
p = s;
}
cnt[p] ++ ;
}
int search()
{
int p = 0,res = 0;
for(int i = 0;str[i];i ++)
{
int &s = son[p][str[i] - 'a'];
if(!s) break;
p = s;
res += cnt[p];
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
{
scanf("%s",str);
insert();
}
while(m --)
{
scanf("%s",str);
printf("%d\n",search());
}
return 0;
}
接下来两道题很相似
最大异或对
这个就是把每个数整成二进制,然后加入trie中。然后对每一个数进行比对,因为求最大,所以每次选择这一位为1还是0的时候尽量选择相反的,没有就选相同的。
#include<bits/stdc++.h>
using namespace std;
const int N = 100009,M = 3000009;
int son[M][2],idx;
int n;
int a[N];
void insert(int x)
{
int p = 0;
for(int i = 30;i >= 0;i --)
{
int &s = son[p][(x >> i) & 1];
if(!s) s = ++ idx;
p = s;
}
}
int search(int x)
{
int res = 0,p = 0;
for(int i = 30;i >= 0;i --)
{
int s = x >> i & 1;
if(son[p][!s])
{
res += 1 << i;
p = son[p][!s];
}
else
{
p = son[p][s];
}
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%d",&a[i]);
insert(a[i]);
}
int res = 0;
for(int i = 1;i <= n;i ++)
{
res = max(res,search(a[i]) );
}
cout << res;
return 0;
}
最长异或值路径
这个题只有一个地方需要处理一下,由于这是一个树状结构,所以从i到j的路径会是
a
[
i
]
x
o
r
a
[
j
]
a[i] xor a[j]
a[i]xora[j]。因为a[i]到a[j]可以看成是从a[i]到根节点,再到a[j]。这两个值异或起来,会将中间重复的部分异或掉。
然后其他麻烦的地方就是建一棵树了
#include<bits/stdc++.h>
using namespace std;
const int N = 100009,M = 3000009;
int son[M][2],idx;
int n;
int a[N];
int h[N],e[N * 2],c[N * 2],ne[N * 2],cnt;
void add(int u,int v,int w)
{
e[cnt] = v,c[cnt] = w,ne[cnt] = h[u],h[u] = cnt ++;
}//链表存
void dfs(int u,int fa,int sum)
{
a[u] = sum;//添加异或值
for(int i = h[u];i >= 0;i = ne[i])
{
int j = e[i];
if(j != fa)//这条与u相连的不能是它的父结点
dfs(j,u,sum ^ c[i]);//添加时异或值
}
}
void insert(int x)
{
int p = 0;
for(int i = 30;i >= 0;i --)
{
int &s = son[p][(x >> i) & 1];
if(!s) s = ++ idx;
p = s;
}
}
int search(int x)
{
int res = 0,p = 0;
for(int i = 30;i >= 0;i --)
{
int s = x >> i & 1;
if(son[p][!s])
{
res += 1 << i;
p = son[p][!s];
}
else
{
p = son[p][s];
}
}
return res;
}
int main()
{
memset(h,-1,sizeof(h));
int u,v,w;
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);//加边
add(v,u,w);
}
dfs(0,-1,0);//建树
for(int i = 1;i <= n;i ++)
{
// cout<<a[i]<<" ";
insert(a[i]);//添加异或值
}
// cout<<endl;
int res = 0;
for(int i = 1;i <= n;i ++)
{
res = max(res,search(a[i]) );//查找最大的
}
cout << res;
return 0;
}