专栏文章

题解:P11843 [USACO25FEB] The Best Subsequence G

P11843题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mink2zh9
此快照首次捕获于
2025/12/02 03:42
3 个月前
此快照最后确认于
2025/12/02 03:42
3 个月前
查看原文
现在询问了区间 [l,r][l,r],要求拿出来长度为 kk 的子序列并且字典序最大。
如果 [l,r][l,r]11 的个数大于等于 kk,那么就可以直接输出答案了,因为这 kk 个数都是 11,一定是字典序最大的,所以此时的答案就是 1+2+22++2k1=2k11+2+2^2+\cdots+2^{k-1}=2^k-1
如果 [l,r][l,r]11 的个数小于 kk,我们希望让前面的 11 尽可能多(因为要字典序最大嘛),所以考虑找到一个分界点 pp,使得 [l,p][l,p] 中只能取 11,而 [p+1,r][p+1,r] 中只能全部取走,目的是接纳一些 00
随着 ppll 取到 rr,明显 [l,p][l,p]11 的个数越来越多,[p+1,r][p+1,r]00 的个数越来越少。所以可以二分最大分界点 pp,使得 pp 是满足 [l,p][l,p]11 的个数加上 [p+1,r][p+1,r] 的长度大于等于 kk 的。
所以我们只需要快速维护一段区间内 11 的个数,一段区间的哈希值(就是按照题意哈希)。
我们需要把之前的全部更新拆成若干个块,这些块的个数是 O(M)O(M) 量级的。然后每个块中都是 11 或者 00 了。然后对于每个块而言,维护前缀 11 的个数以及前缀的哈希值,这样整块的 11 的个数或者哈希值就好求了。
然后就是一些散块的值,这些的细节比较多,看看代码吧。
CPP
#include<bits/stdc++.h>
#define ll long long
using namespace std;
constexpr ll maxn=2e5+5,modd=1e9+7,maxm=5e5+5;
inline ll pw(ll b){
    //快速幂
    ll ret=1,a=2;
    while(b){
        if(b&1)ret=(ret*a)%modd;
        a=(a*a)%modd;
        b>>=1;
    }
    return ret;
}
ll n,m,q,col[maxm],t1[maxm],hsh[maxm],fir,col1;
/*
col: 每个块的yanse
t1:前缀 1 的个数
hsh:前缀的哈希值
*/
map<ll,ll> mp;
vector<ll> vec;
ll bel(ll x){
    //查询一个下标 x 在哪一个块中
    return upper_bound(vec.begin(),vec.end(),x)-vec.begin()-1;
}
ll query1(ll l,ll r){
    ll L=bel(l),R=bel(r);
    if(L==R)return col[L]*(r-l+1);//如果相等就是区间长度
    else return col[L]*(vec[L+1]-l)+//前面的散块
                t1[R-1]-t1[L]+//中间的整块
                col[R]*(r-vec[R]+1);//后边的散块
}
ll query0(ll l,ll r){
    return r-l+1-query1(l,r);
}
ll queryBlock(ll L,ll R){
    //返回一段连续整块的哈希值
    return (hsh[R-1]-hsh[L]*pw(vec[R]-vec[L+1])%modd+modd)%modd;
}
ll queryHash(ll l,ll r){
    //计算 [l,r] 之间的哈希值
    ll L=bel(l),R=bel(r);
    if(L==R)return col[L]*(pw(r-l+1)-1)%modd;//如果在同一个整块中
    else return (queryHash(l,vec[L+1]-1)*pw(r-(vec[L+1]-1))%modd+//前面的散块
                queryBlock(L,R)*pw(r-(vec[R]-1))%modd+//中间的整块
                queryHash(vec[R],r))%modd;//后面的散块
}
ll getpos(ll L,ll R,ll k){
    ll l=L,r=R,md,ret=L,cnt1=query1(L,R);
    //求出最大下标 p,满足 [l,p] 中 1 的个数加上 [p+1,r] 的长度大于等于 k。
    while(l<=r){
        md=(l+r)>>1;
        if(query0(md,R)+cnt1>=k){
            ret=md;
            l=md+1;
        }else{
            r=md-1;
        }
    }
    return ret;
}
int main(){
    //freopen("1.in","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>q;
    mp[1]=mp[n+1]=2;
    for(int i=1;i<=m;i++){
        ll l,r;
        cin>>l>>r;
        mp[l]^=1;
        mp[r+1]^=1;
    }
    col1=mp[1]&1;
    for(auto i:mp){
        if(i.second){
            col[vec.size()]=((vec.size()&1)^(col1));
            vec.push_back(i.first);
        }
    }
    //对于每一个块求出前缀 1 的个数和前缀哈希值
    for(int i=0;i<vec.size()-1;i++){
        if(i!=0){
            t1[i]=t1[i-1];
            hsh[i]=hsh[i-1]*pw(vec[i+1]-vec[i])%modd;
        }
        if(col[i]){
            t1[i]+=vec[i+1]-vec[i];
            hsh[i]=(hsh[i]+pw(vec[i+1]-vec[i])-1)%modd;
        }
    }
    while(q--){
        ll l,r,k;
        cin>>l>>r>>k;
        if(query1(l,r)>=k){
            //1 的个数大于等于 k
            cout<<(pw(k)-1+modd)%modd<<"\n";
            continue;
        }
        ll id=getpos(l,r,k);//求出分界点
        ll pre1=query1(l,r)-query1(id,r);
        //前面的 1 拼上后边的哈希值
        cout<<((pw(pre1)-1)*pw(r-id+1)%modd+queryHash(id,r)%modd+modd)%modd<<"\n";
    }
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...