题目链接:https://www.luogu.com.cn/problem/P4875
P4875 [USACO14OPEN]Fair Photography G
哈希
题目大意为:给定数轴上
n
n
n 头牛,每头牛拥有一个坐标
x
i
x_i
xi 与种类
b
i
b_i
bi ,一个区间合法当且仅当区间内出现了至少
k
k
k 头牛且不同种类的牛出现的个数相同,求最长合法区间。其中牛的种类数
m
≤
8
m\le8
m≤8 ,限制条件
k
≥
2
k\ge2
k≥2 。
对于区间类问题,一般常用枚举一端并快速判断另一端的方法。考虑到此题中的种类数
m
≤
8
m\le8
m≤8 ,对于相同种类的牛,可以用一个前缀和数组来表示该类牛出现的信息,假设用数组
s
u
m
i
,
j
sum_{i,j}
sumi,j 表示前
i
i
i 头牛中
j
j
j 类牛的总数,那么一个区间
[
l
,
r
]
[l,r]
[l,r] 合法的条件即为:
对于所有在
[
l
,
r
]
[l,r]
[l,r] 出现的
j
j
j ,有
s
u
m
r
,
j
−
s
u
m
l
−
1
,
j
sum_{r,j}-sum_{l-1,j}
sumr,j−suml−1,j 相等。设改区间内出现的最小的种类
j
j
j 为
f
i
fi
fi ,转换条件可得,对于所有在
[
l
,
r
]
[l,r]
[l,r] 出现的
j
j
j ,有
s
u
m
r
,
j
−
s
u
m
l
−
1
,
j
=
=
s
u
m
r
,
f
i
−
s
u
m
l
−
1
,
f
i
sum_{r,j}-sum_{l-1,j}==sum_{r,fi}-sum_{l-1,fi}
sumr,j−suml−1,j==sumr,fi−suml−1,fi ,即
s
u
m
r
,
j
−
s
u
m
r
,
f
i
=
=
s
u
m
l
−
1
,
j
−
s
u
m
l
−
1
,
f
i
sum_{r,j}-sum_{r,fi}==sum_{l-1,j}-sum_{l-1,fi}
sumr,j−sumr,fi==suml−1,j−suml−1,fi ,每个出现的种类
j
j
j 在前缀中的数量与
f
i
fi
fi 在前缀中的数量相等。那么假设当前枚举到的右端点为
i
i
i ,计算出所有的
s
u
m
i
,
j
−
s
u
m
i
,
f
i
sum_{i,j}-sum_{i,fi}
sumi,j−sumi,fi 后,我们只需知道所有出现种类相同且
s
u
m
l
,
j
−
s
u
m
l
,
f
i
sum_{l,j}-sum_{l,fi}
suml,j−suml,fi 与
i
i
i 完全相等的最左位置
l
l
l 即可,此时可更新答案为
i
−
l
i-l
i−l 。
对于快速判断
s
u
m
l
,
j
−
s
u
m
l
,
f
i
sum_{l,j}-sum_{l,fi}
suml,j−suml,fi 的状态,考虑到
m
≤
8
m\le8
m≤8 ,可以采用哈希的方式来快速表示一个状态,对于区间
[
l
,
i
]
[l,i]
[l,i] 中出现的牛的种类,因为我们确定了右端点
i
i
i ,那么考虑
l
l
l 在向左移动时,区间内的牛种类数只会增加不会减少,因此我们可以记录每种种类的牛出现的最靠右的位置,每次端点
l
l
l 向左移时按出现位置顺序将对应的种类放入哈希考虑的范围内,依次根据哈希值表示的位置更新答案即可。
对于哈希值的更新,相应的,在右端点
i
i
i 移动时,每次会加入一头新的牛,假设与新加入牛最近的相同种类牛的位置为
p
r
e
pre
pre ,那么加入的牛只会改变区间
[
p
r
e
+
1
,
i
]
[pre+1,i]
[pre+1,i] 这一区间内的哈希值,由于总种类数
m
≤
8
m\le8
m≤8 ,每次暴力修改即可,总次数不会超过
8
∗
n
8*n
8∗n 。综上,加上查询的次数,总复杂度约为
O
(
8
2
∗
n
)
O(8^2*n)
O(82∗n) 。
#include<bits/stdc++.h>
using namespace std;
using ull=unsigned long long;
using pii=pair<int,int>;
constexpr ull base=997;
constexpr int N=1e5+5;
unordered_map<ull,int> app;
int n,m;
int pre[8];
int p[8];
int sum[N][8];
pii a[N];
ull calc(int s,int i){
ull hs=0;
int fi=7;
for(int j=0;j<8;j++) if((s>>j)&1){
fi=j;
break;
}
for(int j=fi+1;j<8;j++) if((s>>j)&1) hs=hs*base+sum[i][j]-sum[i][fi];
return hs;
}
int main(){
iota(p,p+8,0);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].first,&a[i].second),a[i].second--;
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
for(int j=0;j<8;j++) sum[i][j]+=sum[i-1][j];
sum[i][a[i].second]++;
}
int ans=-1;
for(int i=1;i<=n;i++){
app[0]=a[i].first;
for(int j=pre[a[i].second]+1;j<=i;j++){
int s=0;
for(int k=0;k<8;k++) if(sum[i-1][k]-sum[j-1][k]) s|=(1<<k);
ull hs=calc(s,j-1);
app.erase(hs);
s|=(1<<a[i].second);
hs=calc(s,j-1);
if(!app.count(hs)) app[hs]=a[j].first;
}
pre[a[i].second]=i;
sort(p,p+8,[&](const int &a,const int &b){
return pre[a]>pre[b];
});
int s=0;
for(int j=0;j<m-1;j++) s|=(1<<p[j]);
for(int j=m-1;j<8&&pre[p[j]];j++){
s|=(1<<p[j]);
ull hs=calc(s,i);
if(app.count(hs)) ans=max(ans,a[i].first-app[hs]);
}
}
printf("%d",ans);
return 0;
}