P4875 [USACO14OPEN]Fair Photography G(哈希)

题目链接: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 m8 ,限制条件 k ≥ 2 k\ge2 k2
对于区间类问题,一般常用枚举一端并快速判断另一端的方法。考虑到此题中的种类数 m ≤ 8 m\le8 m8 ,对于相同种类的牛,可以用一个前缀和数组来表示该类牛出现的信息,假设用数组 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,jsuml1,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,jsuml1,j==sumr,fisuml1,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,jsumr,fi==suml1,jsuml1,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,jsumi,fi 后,我们只需知道所有出现种类相同且 s u m l , j − s u m l , f i sum_{l,j}-sum_{l,fi} suml,jsuml,fi i i i 完全相等的最左位置 l l l 即可,此时可更新答案为 i − l i-l il
对于快速判断 s u m l , j − s u m l , f i sum_{l,j}-sum_{l,fi} suml,jsuml,fi 的状态,考虑到 m ≤ 8 m\le8 m8 ,可以采用哈希的方式来快速表示一个状态,对于区间 [ 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 m8 ,每次暴力修改即可,总次数不会超过 8 ∗ n 8*n 8n 。综上,加上查询的次数,总复杂度约为 O ( 8 2 ∗ n ) O(8^2*n) O(82n)

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值