链表
CH1301
首先想到的是暴力算法,对于每个
A
i
A_i
Ai我找
A
1
A_1
A1 到
A
i
−
1
A_{i-1}
Ai−1 中
∣
A
i
−
A
x
∣
|A_i - A_x|
∣Ai−Ax∣的最小值
(
1
≤
x
≤
i
−
1
)
(1 \le x \le i-1)
(1≤x≤i−1),时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。用数据结构进行加速,堆是找一堆数中的最值所以不行,有序数组可以,
A
i
A_i
Ai附近的两个数就是候选的(因为数组里面的元素都是各不相同的),因为未来的数可能插入到有序数组的任意位置,即有序数组的任何位置在未来都会有用,所以单调队列是不行的,问题是每次更新单调数组怎么使其复杂度不为n,没想出来,康康书上怎么写的:
书上有两个方法,在我看来 可以这么分类,一种是边遍历边排序 另一种是一开始就排好序:
边遍历边排序 用set结构 set结构是一个二叉平衡树 支持查找前驱和后继的功能
#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;
typedef pair<int,int> P;
set<P> s;
int main()
{
int n,x;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
s.insert({x,i});
if(i == 1) continue;
set<P>::iterator ind = s.find({x,i});
int mn = INT_MAX,p_i;
if(++ind != s.end()){
mn = min(mn,ind->first - x);
p_i = ind->second;
}
ind = s.find({x,i});
if(ind != s.begin() && x - (--ind)->first <= mn){
mn = x - ind->first;
p_i = ind->second;
}
printf("%d %d\n",mn,p_i);
}
return 0;
}
由于s.end() 和 ind都不支持 +1 和减1操作,所以这里进行了两次find,因为set是由平衡二叉树实现,所以每次插入的时候时间复杂度为 O ( l o g n ) O(logn) O(logn) 插入完了以后set里面的所有元素是有序的 这个代码是读了光盘里面的代码 自己按照光盘的思想来写的
对于一开始就排好序的方法:
#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 value,ind,prv,nxt;
bool operator < (const A x)const {
return this->value < x.value;
}
}a[N];
struct ANS{
int mn,ind;
}ans[N];
int b[N];//数组b为数组a的序号数组,即b[1]为a[1]在整个a数组的排名
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i].value);
a[i].ind = i;
}
sort(a+1,a+n+1);//按照A中value的值进行排序,ind是记录排序之前的序号
for(int i=1;i<=n;i++){
b[a[i].ind] = i;//构造b数组,未排序之前的a[i]在整个数组的排名为现在的b[i]
a[i].nxt = i+1;//用静态数组来构造链表,这个链表是原数组排序后形成的链表
a[i].prv = i-1;
}
int left = 1,right = n;//left和right均指着链表的边界
for(int i=n;i>=1;i--){
if(b[i] == right){//数a[b[i]]是在目前所有数的最右边,也就是a[b[i]].nxt是不存在的
ans[i].mn = a[b[i]].value - a[a[b[i]].prv].value;//与a[i]相邻的数只有a[a[b[i]].prv].value a[b[i]].prv是指向排名b[i]前面数的序号
ans[i].ind = a[a[b[i]].prv].ind;
right = a[right].prv;//修改相应边界right
}
else if(b[i] == left){//数a[b[i]]是在目前所有数的最左边,也就是a[b[i]].prv是不存在的
ans[i].mn = a[a[b[i]].nxt].value - a[b[i]].value;//与a[i]相邻的数只有a[a[b[i]].nxt].value a[b[i]].nxt是指向排名b[i]后面数的序号
ans[i].ind = a[a[b[i]].nxt].ind;
left = a[left].nxt;//修改相应边界left
}
else{//处在非边界的时候,数a[b[i]]]的nxt和prv都存在 取最小的
ans[i].mn = a[a[b[i]].nxt].value - a[b[i]].value;
ans[i].ind = a[a[b[i]].nxt].ind;
if(a[b[i]].value - a[a[b[i]].prv].value <= ans[i].mn){
ans[i].mn = a[b[i]].value - a[a[b[i]].prv].value;
ans[i].ind = a[a[b[i]].prv].ind;
}
}
a[a[b[i]].prv].nxt = a[b[i]].nxt;//修改链表结构
a[a[b[i]].nxt].prv = a[b[i]].prv;
}
for(int i=2;i<=n;i++)
printf("%d %d\n",ans[i].mn,ans[i].ind);
return 0;
}
注意这个题的核心思想,也就是算法正确的重要因素是
算法是从最后一个数往前面进行遍历!!!(原数组是指未对a进行排序时的数组)
原数组的a[n] 排行为b[n] 因为是最后一个数,所以其左边的所有数都在数组中,如果是从左边向右边遍历(即原数组的a[3]的左边的a[1]和a[2] 你不知道是a[1]在前还是a[2]在前 因为这两个数在链表里面不一定连续,仅需排序以后a[3]的左边和右边不一定在a[1]和a[2]里面,如果是从右边向左边遍历 那么a[i]排序后的数的左边和右边一定在链表里面,而且每次遍历完以后会将遍历过的数移除链表从而保证链表里面的数 都是原a[i]左边的数
时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
POJ3784 Running Median
从第一个数开始每插入两个数就要输出一个数,已知上一次插入的中位数,由这几个信息看是否能起到加速的作用,设上一次插入后得到的中位数是m,接着插入的数为a和b,如果
m
i
n
(
a
,
b
)
≤
m
≤
m
a
x
(
a
,
b
)
min(a,b) \le m \le max(a,b)
min(a,b)≤m≤max(a,b) 那么m仍然是中位数,如果
m
i
n
(
a
,
b
)
<
m
a
x
(
a
,
b
)
<
m
min(a,b) < max(a,b) < m
min(a,b)<max(a,b)<m,那么中位数是小于
m
m
m 但不一定为
m
i
n
(
a
,
b
)
min(a,b)
min(a,b)或者
m
a
x
(
a
,
b
)
max(a,b)
max(a,b),因为是中位数,插入两个数后中位数离m的距离很近,假设m为中位数的时候有2n+1个数,m就为第n+1个数,左边的数为x 右边的数为y,加了两个数以后中位数在第n+2个,m可能的位置有n+1,n+2,n+3,中位数的值 只可能在
m
i
n
(
a
,
b
)
min(a,b)
min(a,b)、
m
m
m、
x
x
x、
y
y
y、
m
a
x
(
a
,
b
)
max(a,b)
max(a,b)中的一个,我们就根据
m
i
n
(
a
,
b
)
min(a,b)
min(a,b)和
m
a
x
(
a
,
b
)
max(a,b)
max(a,b)与其它三个数的情况来进行讨论,得到x和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 = 10004;
int median[N];//存放每次输出的中位数
int main()
{
int p,dummy,n,ind,a,b;
scanf("%d",&p);
while(p--){
ind = 0;
priority_queue<int,vector<int>,greater<int> > sheap;//在获取第i个中位数之前,sheap是存放所有大于第i-1个中位数的数
priority_queue<int,vector<int>,less<int> > bheap;//在获取第i个中位数之前,sheap是存放所有小于第i-1个中位数的数
scanf("%d%d",&dummy,&n);
scanf("%d",&median[ind++]);
n--;
while(n){
scanf("%d%d",&a,&b);
if(ind == 1){//对于刚开始 原数组只有一个元素时,median[ind-1]为上一次的中位数
int mn = min(a,min(b,median[ind-1]));//mn为 a,b,median[ind-1]的最小值
int mx = max(a,max(b,median[ind-1]));//mn为 a,b,median[ind-1]的最大值
median[ind] = a - mn + b - mx + median[ind-1];// 三个数 减去其中最大值和最小值 就能得到中间值,第二次中位数
ind++;
bheap.push(mn);//mn小于第二个中位数median[1] 所以放入bheap中
sheap.push(mx);//mx大于第二个中位数median[1] 所以放入bheap中
}
else{
if(min(a,b) <= median[ind-1] && median[ind-1] <= max(a,b)){//加入的a和b 一个在上一次中位数median[ind-1]的左边,另一个在median[i-1]的右边,中位数还是为median[i-1]
median[ind] = median[ind-1];
ind++;
bheap.push(min(a,b));//小于median[i]的数放入bheap
sheap.push(max(a,b));//大于median[i]的数放入sheap
}
else if(max(a,b) <= median[ind-1]){//a,b中最大的数小于等于上一次的中位数,a,b不可能同时等于median[i-1]因为这种情况会在上面的if处理完,所以max(a,b)<=median[i-1]那么min(a,b)<median[i-1]
sheap.push(median[ind-1]);//median[i-1]左边加了两个数,所以median[i-1]一定在median[i]的右边,所以加入到sheap中
int x = bheap.top();//这是上次小于median[i-1]的最大值
if(x >= max(a,b)){//说明a,b都在median[i]左边,median[i]就为x
median[ind++] = x;
bheap.pop();
bheap.push(min(a,b));
bheap.push(max(a,b));
}
else{
median[ind++] = max(a,b);//x<max(a,b) min(a,b)<max(a,b) 那么median[i]就为max(a,b)
bheap.push(min(a,b));
}
}
else{//这里对应的是min(a,b) >= median[ind-1] 情况与上个else if类似
bheap.push(median[ind-1]);
int y = sheap.top();
if(y <= min(a,b)){
median[ind++] = y;
sheap.pop();
sheap.push(min(a,b));
sheap.push(max(a,b));
}
else{
median[ind++] = min(a,b);
sheap.push(max(a,b));
}
}
}
n-=2;
}
printf("%d %d\n",dummy,ind);
for(int i=0;i<ind;i++){
printf("%d ",median[i]);
if(i>0 && i%10 == 9)
puts("");
}
puts("");
}
return 0;
}
while里面有堆的操作,时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn),所以总的时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
这个题书上给的思路是一个离线算法,先将所有数进行排序,和上一题一样用链表,从n到1,每次通过删除链表的元素来保证每次求的都是范围内的数
#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 = 10004;
struct A{
int value,index,prv,nxt;
bool operator < (const A x) const{
return this->value < x.value;
}
}a[N];
int b[N],ans[N];
int main()
{
int p,dummy,n,ind,median,flag;
scanf("%d",&p);
while(p--){
scanf("%d%d",&dummy,&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i].value);
a[i].index = i;
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
b[a[i].index] = i;
a[i].prv = i-1;
a[i].nxt = i+1;
}
printf("%d %d\n",dummy,(n+1)/2);
median = (n+1)/2;
ind = median;
ans[ind] = a[median].value;
ind--;
for(int i=n;i >= 3;i-=2){
flag = 0;
if(a[b[i]].value > a[median].value) flag++;
else if(a[b[i]].value < a[median].value) flag--;
if(a[b[i-1]].value > a[median].value) flag++;
else if(a[b[i-1]].value < a[median].value) flag--;
if(flag > 0) median = a[median].prv;
else if(flag == 0) median = median;//删除一个大于a[median] 另一个小于a[median] 中位数不变
else median = a[median].nxt;
ans[ind] = a[median].value;
a[a[b[i]].prv].nxt = a[b[i]].nxt;//删除a[b[i]] 即原数组的a[i],更新相应链表结构
a[a[b[i]].nxt].prv = a[b[i]].prv;
a[a[b[i-1]].prv].nxt = a[b[i-1]].nxt;//删除a[b[i-1]] 即原数组的a[i-1],更新相应链表结构
a[a[b[i-1]].nxt].prv = a[b[i-1]].prv;
ind--;
}
for(int i=1;i<=(n+1)/2;i++){
if(i % 10 !=0)
printf("%d ",ans[i]);
else
printf("%d\n",ans[i]);
}
puts("");
}
return 0;
}
算法的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 主要是因为排序。因为排好序的链表,对于全部输入,所有数都在里面,所以中位数一定是a[(n+1)/2].value,对于全部输入之前,也就是最后加入两个数之前,中位数一定是刚才a[(n+1)/2]的前面或者后面,因为删了两个数,中位数偏移最大是删的两个数都大于a[(n+1)/2] 或者都小于a[(n+1)/2],都大于的情况中位数会往前移一格。都小于的情况中位数就会往后移一格。
Hash
POJ3349 Snowflake Snow Snowflakes
由书上的介绍,Hash的一个经典的例子是判断一个超大数组中每个数值的个数,感觉这个有点像群论里面求可交换群的个数,与123456等价的有234561 345612 456123 561234 612345 654321 543216 432165 321654 216543 165432,每六个不同的数有12个等价数,6个不同的数有720种排列,就有60个等价类,如何根据12个数的特点来进行辨别呢?
书上给的方法是先通过hash来筛一遍,hash值相同的再手动比较,这样会减少很多时间。
hash函数的定义是
H
(
a
i
,
1
,
a
i
,
2
,
a
i
,
3
,
a
i
,
4
,
a
i
,
5
,
a
i
,
6
)
=
(
∑
j
=
1
6
a
i
,
j
+
∏
j
=
1
6
a
i
,
j
)
m
o
d
P
H(a_{i,1},a_{i,2},a_{i,3},a_{i,4},a_{i,5},a_{i,6}) = (\sum_{j=1}^{6}a_{i,j} + \prod_{j=1}^6a_{i,j}) mod P
H(ai,1,ai,2,ai,3,ai,4,ai,5,ai,6)=(∑j=16ai,j+∏j=16ai,j)modP 取P为最接近的质数,书上P取的是99991(取质数的原因是散列均匀 平均冲突小)
#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 MOD = 99991;
const int N = 100004;
int head[N],nxt[N],var[N][6],tot=0;
int Hash(int* a){
long long sum = 0,product = 1;
for(int i=0;i<6;i++){
sum = (sum + a[i]) % MOD;
product = (product * a[i]) % MOD;
}
return (sum + product) % MOD;
}
bool is_equal(int* a,int* b){
for(int i=0;i<6;i++){
if(a[0] == b[i]){
if ((a[1] == b[(i+1)%6] &&//从相等开始 依次顺时针和逆时针比较相应位置
a[2] == b[(i+2)%6] &&
a[3] == b[(i+3)%6] &&
a[4] == b[(i+4)%6] &&
a[5] == b[(i+5)%6]) ||
(a[1] == b[(i+5)%6] &&
a[2] == b[(i+4)%6] &&
a[3] == b[(i+3)%6] &&
a[4] == b[(i+2)%6] &&
a[5] == b[(i+1)%6]))
return true;
}
}
return false;
}
bool insert(int* a){
int x = Hash(a);//首先根据hash函数 计算出hash值
for(int i=head[x];i;i=nxt[i])//如果发生冲突,即head[x]的值不为0,当nxt[i]为0 说明链表遍历完毕
if(is_equal(a,var[i])) return true;//对于每个冲突项进行is_equal检查,如果是相同的那么就返回true
tot++;
memcpy(var[tot],a,sizeof(int)*6);//将新值a[0]..a[5]插入到var[tot]里面
nxt[tot] = head[x];//修改对应链表的结构
head[x] = tot;
return false;
}
int main()
{
int n,t[6];
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=0;j<6;j++)
scanf("%d",&t[j]);
if(insert(t)){//插入到hash表中,如果返回为true,说明检测到相同值 这时打印Twin snowflakes found.
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");//所有数都插入到hash表中 且没有发现相同的,此时打印No two snowflakes are alike.
return 0;
}
hash平均复杂度为 O ( 1 ) O(1) O(1) 所以这个解法的平均时间复杂度为 O ( n ) O(n) O(n)
CH1401 兔子与兔子
这个题也是判断数组里面是否有两个相同的数,上一题数组的元素是一个含6个元素的数组,这一题数组的元素是字符串的数组。我的想法跟上题一样 先hash hash相等了再进行判等
我构造数组
p
p
p
p
[
1
]
p[1]
p[1]表示第一个字符的hash值,
p
[
i
]
p[i]
p[i]表示data字符串的子字符串
d
a
t
a
[
1...
i
]
data[1...i]
data[1...i]的hash值,
对于
d
a
t
a
data
data子串
[
i
.
.
.
j
]
[i...j]
[i...j]的hash值 就为
p
[
j
]
−
p
[
i
−
1
]
∗
b
a
s
e
j
−
i
m
o
d
M
O
D
p[j] - p[i-1]*base^{j-i} \mod MOD
p[j]−p[i−1]∗basej−imodMOD
weight数组
w
e
i
g
h
t
[
i
]
weight[i]
weight[i]表示
b
a
s
e
j
−
i
m
o
d
M
O
D
base^{j-i}\mod MOD
basej−imodMOD
#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 MOD = 999983;
const int N = 1000004;
char data[N];
int p[N],weight[N];
int Hash(int l,int r){
return (p[r] - (long long)p[l-1] * weight[r-l+1] % MOD + MOD) % MOD;
}
bool is_equal(int l1,int r1,int l2,int r2){
if(r1-l1 == r2-l2){
int flag = 1;
for(int k=0;k<=r1-l1;k++)
if(data[l1+k] != data[l2+k]){
flag = 0;
break;
}
return flag;
}
return false;
}
int main()
{
int m,n;
scanf("%s",data);
n = strlen(data);
p[0] = 0,weight[0] = 1;
for(int i=1;i<=n;i++){
p[i] = ((long long)p[i-1]*26%MOD + data[i-1]) % MOD;
weight[i] = (long long)weight[i-1] * 26 % MOD;
}
scanf("%d",&m);
while(m--){
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(Hash(l1,r1) == Hash(l2,r2) && is_equal(l1,r1,l2,r2))
puts("Yes");
else
puts("No");
}
return 0;
}
这个题超时了,我换了一组
b
a
s
e
=
31
base = 31
base=31和
M
O
D
=
1000000007
MOD=1000000007
MOD=1000000007 还是超时,看了一下书上的代码,它没有is_equal的检查 于是我把is_equal给去了 就AC了,感觉有点悬啊 这代码靠概率AC的,去掉以后时间复杂度为
O
(
n
)
O(n)
O(n)
书上的代码 思路和我差不多,区别在于书上选的base为131,MOD为
2
64
2^{64}
264 MOD取
2
64
2^{64}
264的好处是用unsigned long long 相加相乘,溢出的话相当于取模
对于我代码里面
d
a
t
a
[
i
−
1
]
data[i-1]
data[i−1]和
p
[
i
]
p[i]
p[i]的索引没有对齐,书上用这个方法对齐了:
scanf("%s",s + 1);
int n = strlen(s+1);
书上对于这个直接hash判等的方法给的一个解释是:除了在极特殊构造的数据上,上述Hash算法很难产生冲突,一般情况下上述Hash算法完全可以出现在题目的标准答案中。我们还可以夺取一些恰当的P和M的值,多进行几组Hash运算
CH1402 后缀数组
后缀数组对我来说很有吸引力,但是很难理解,通过这题来了解一下后缀数组,题目是要通过快排,Hash和二分来实现后缀数组,既然要用快排,那么就要实现相邻数进行比较,快排本身的时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),题目要求的是时间复杂度为
O
(
n
(
l
o
g
n
)
2
)
O(n(logn)^2)
O(n(logn)2),即字符串进行比较的时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn),根据题目上写的,应该是用Hash和二分来实现这个
O
(
l
o
g
n
)
O(logn)
O(logn)的比较,Hash是用来判等的,不具备比较大小的能力,用二分法能判大小吗?想不出来,看书,发现比较后缀字符串p和q,即比较S[p ~ n]和S[q ~ n] 可以二分长度min(n-p+1,n-q+1),每次的二分点mid 比较S[p ~ p+mid-1]和S[q ~ q+mid-1],通过hash值进行比较,一直找到最小的low使S[p ~ p+low-1]和S[q ~ q+low-1]相等,然后再比较S[p+low]和S[q+low],哪个小说明哪个的字典序小,得到sa数组以后,来求height数组也可以用这个方法! 我用上题书中提到的方法,即用
2
64
2^{64}
264来取模,用unsigned long long来表示
#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 = 300004;
char data[N];
unsigned long long p[N],weight[N];
int n;
int Hash(int l,int r){
return p[r] - p[l-1] * weight[r-l+1];
}
int comp(const void* a,const void* b){
int p = *(int*)a,q = *(int*)b;
int low = 1,high = min(n - q + 1,n - p + 1);//low and high are both length
while(low < high){
int mid = (low + high) >> 1;
if(Hash(p,p+mid-1) != Hash(q,q+mid-1))
high = mid;
else
low = mid + 1;
}
if(low == min(n - q + 1,n - p + 1) && data[p+low-1] == data[q+low-1]) return q - p;//low等于最大长度时,最大长度对应的字符可能相等也可能不等,若相等,最短的字符串到头了,那么这个字符串一定是最小的
else return data[p+low-1] - data[q+low-1];
}
int main()
{
scanf("%s",data+1);
n = strlen(data+1);
vector<int> sa(n+1),height(n+1);
for(int i=1;i<=n;i++)
sa[i] = i;
p[0] = 0,weight[0] = 1;
for(int i=1;i<=n;i++){
p[i] = p[i-1] * 131 + data[i];
weight[i] = weight[i-1] * 131;
}
qsort(&sa[1],n,sizeof(int),comp);//注意这里的写法,用sa.begin()+1是不行的
height[1] = 0;
for(int i=2;i<=n;i++){
int low = 1,high = min(n-sa[i]+1,n-sa[i-1]+1);
while(low < high){
int mid = (low + high) >> 1;
if(Hash(sa[i-1],sa[i-1]+mid-1) != Hash(sa[i],sa[i]+mid-1))
high = mid;
else low = mid + 1;
}
height[i] = data[sa[i-1]+low-1] == data[sa[i]+low-1] ? low : low - 1;
}
for(int i=1;i<=n;i++)
printf("%d ",sa[i]-1);
puts("");
for(int i=1;i<=n;i++)
printf("%d ",height[i]);
puts("");
return 0;
}
康康光盘里面的代码
光盘里面用的是sort来进行sa数组的排序而非我这边的qsort,sort的模型是bool sort(int x,int y)
他求最长公共前缀lcp,二分+Hash的代码:
int lcp(int x,int y) {
int l=0, r=min(n-x+1,n-y+1);
while(l<r) {
int mid=(l+r+1)>>1;
if(get_hash(x,x+mid-1)==get_hash(y,y+mid-1)) l=mid; else r=mid-1;
}
return l;
}
他的二分和我的二分方向不一样,我是判不等他是判等,l的位置是相等的位置,r的位置是不等的位置,他从长度0开始最后返回的就是相等的数
趁着这个机会 我就去学学标准的后缀数组!
构造后缀数组,构造sa函数,读下面的代码建议先百度搜搜后缀数组的博文
这个代码很漂亮,要细细地品(漂亮是品的吗?),第一遍我能品出个大概,后来有机会再继续品!
void sufarr(int n)
{
int i, p, l, m = 200;//m在这里是字符的总数
for (i = 0;i < m;i++) c[i] = 0;//下面4个for是对单个字符进行基础排序,在这里构造出的rk是单字符的rank
for (i = 0;i < n;i++) c[rk[i] = a[i]]++;
for (i = 1;i < m;i++) c[i] += c[i - 1];
for (i = n - 1;i >= 0;i--) sa[--c[a[i]]] = i;
for (l = p = 1;p < n;l *= 2, m = p)
{
for (p = 0, i = n - l;i < n;i++) sec[p++] = i; //先按第二关键词进行排序,这个for循环是处理第二个关键词不在数组中的,这些关键词就排在有第二关键词的前面,sec按第二关键词排序,里面存的是第一个关键词的序号
for (i = 0;i < n;i++)
if (sa[i] >= l) sec[p++] = sa[i] - l;//然后在存在第二关键词中进行排序,对于sa[i]>=l的所有序号都是有第一个关键词的,sa[i]是按照上次排序的结果,位于sa前面的元素且序号大于等于l也就是在有第二关键词存在排在前面的
for (i = 0;i < n;i++) fir[i] = rk[sec[i]];//对于sec数组的第一关键词序号,将其排名存入到fir中进行基数排序
for (i = 0;i < m;i++) c[i] = 0;
for (i = 0;i < n;i++) c[fir[i]]++;
for (i = 1;i < m;i++) c[i] += c[i - 1];
for (i = n - 1;i >= 0;i--) sa[--c[fir[i]]] = sec[i];
memcpy(sec, rk, sizeof(rk));//将经过第二第一关键词排序的数组放到rk中,
rk[sa[0]] = 0;
for (i = p = 1;i < n;i++)
rk[sa[i]] = comp(sa[i], sa[i - 1], l) ? p - 1 : p++;//根据第一第二关键词进行序号的分配,rk数组里可能会是这样:(1,1),(1,2),(1,2),(1,3),(2,1),(2,2),(2,2) rk[sa[0]] = 0 rk[sa[1]] = 1 rk[sa[2]] = 1 rk[sa[3]] = 2 rk[sa[4]] = 3 rk[sa[5]] = 4 rk[sa[6]] = 4
}
}
a数组是字符数组,a[n]为0 是手动加进去的
计算height数组
void calh()
{
int i, j, k = 0;
for (i = 1;i <= n;i++) rk[sa[i]] = i;
for (i = 0;i < n;h[rk[i++]] = k)
for (k ? k-- : 0, j = sa[rk[i] - 1];a[i + k] == a[j + k];k++);
}
这个式子是根据 h [ r k [ i ] ] ≥ h [ r k [ i − 1 ] ] − 1 h[rk[i]] \ge h[rk[i-1]] - 1 h[rk[i]]≥h[rk[i−1]]−1的性质