倍增/ST表

P3865 【模板】ST 表 && RMQ 问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:区间最大值,模板题。

int n,m;
int arr[100005];
int f[100005][25];     (1<<20)=1e6
void init(){                                o(nlogn)
    for(int i=1;i<=n;i++) f[i][0]=arr[i];
    for(int j=1;j<=20;j++){     枚举区间长度
        for(int i=1;i+(1<<j)-1<=n;i++){    枚举区间起点
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
}
inline int query(int l,int r){         o(1)
    int k=log2(r-l+1);
    return max(f[l][k],f[r-(1<<k)+1][k]);
}
【模板】ST 表 && RMQ 问题
https://www.luogu.com.cn/problem/P3865
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>arr[i];
    init();
    while(m--){
        int l,r; cin>>l>>r;
        cout<<query(l,r)<<endl;
    }
}

P2251 质量检测 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:区间最小值,模板题。

int n,m;
int arr[1000006];
int f[1000006][25];   (1<<20)=1e6
void init(){
    for(int i=1;i<=n;i++) f[i][0]=arr[i];
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r){
    int k=log2(r-l+1);
    return min(f[l][k],f[r-(1<<k)+1][k]);
}
P2251 质量检测
https://www.luogu.com.cn/problem/P2251
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>arr[i];
    init();
    for(int i=1;i+m-1<=n;i++){
        int l=i,r=i+m-1;
        cout<<query(l,r)<<endl;
    }
}

P2880 [USACO07JAN] Balanced Lineup G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:区间极差,也是模板题。

int n,q;
int arr[50004];
int mx[50004][25];
int mi[50004][25];
void init(){
    for(int i=1;i<=n;i++) mx[i][0]=arr[i],mi[i][0]=arr[i];
    for(int j=1;j<=20;j++){     枚举区间长度,因为是2^j,所以永远是偶数
        for(int i=1;i+(1<<j)-1<=n;i++){ 枚举区间起点,i+(1<<j)-1<=n保证了当前枚举的区间起点及长度不会超出n
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
            mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r){
    int k=log2(r-l+1);
    return max(mx[l][k],mx[r-(1<<k)+1][k])-min(mi[l][k],mi[r-(1<<k)+1][k]);
}
纯板子--区间询问极差
P2880 [USACO07JAN] Balanced Lineup G
https://www.luogu.com.cn/problem/P2880
void solve(){
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>arr[i];
    init();
    while(q--){
       int l,r; cin>>l>>r;
       cout<<query(l,r)<<endl;
    }
}

[ABC352D] Permutation Subsequence - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路: 思维题,换个角度(下标)维护区间最大最小值。思想类似用树状数组求逆序对中,桶的作用 转换之后实际上就是求区间长度为k的最小极差。

int n,k;
int arr[200005];
int mx[200005][25];
int mi[200005][25];
void init(){
    for(int i=1;i<=n;i++) mx[i][0]=arr[i],mi[i][0]=arr[i];
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
            mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r){
    int x=log2(r-l+1);
    return max(mx[l][x],mx[r-(1<<x)+1][x])-min(mi[l][x],mi[r-(1<<x)+1][x]);
}
思维题,换个角度(下标)维护区间最大最小值。思想类似用树状数组求逆序对中,桶的作用
转换之后实际上就是求区间长度为k的最小极差
[ABC352D] Permutation Subsequence
https://www.luogu.com.cn/problem/AT_abc352_d
void solve(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        int x; cin>>x;
        arr[x]=i;
    }
    init();
    int ans=INT_MAX;
    for(int i=1;i+k-1<=n;i++) ans=min(ans,query(i,i+k-1));
    cout<<ans;
}

P2866 [USACO06NOV] Bad Hair Day S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:ST表+二分

法二:ST表,比单调栈麻烦,ST表预处理的是每个区间最大值,然后枚举i,二分区间右端点,进行o(1)查询[i+1,mid]是否合法
题目理解为每头牛向右看,有多少个比自己矮的,遇到第一个>=自己的就不算了.
int n;
int arr[80004];
法一:单调栈--题目理解为每个牛向左看,能看到多少个比自己高的,那么就是从左到右维护单调递减的栈
P2866 [USACO06NOV] Bad Hair Day S
https://www.luogu.com.cn/problem/P2866
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i];
    stack<int> stk;
    int ans=0;
    for(int i=1;i<=n;i++){
        if(stk.size()&&arr[i]<stk.top()) ans+=stk.size();
        else if(stk.size()&&arr[i]>=stk.top()){     取等,因为是从右往左看,同等身高的不能算
            while(stk.size()&&arr[i]>=stk.top()) stk.pop();
            ans+=stk.size();
        }
        stk.emplace(arr[i]);
    }
    cout<<ans;
}

int n;
int arr[80004];
int f[80004][25];
void init(){
    for(int i=1;i<=n;i++) f[i][0]=arr[i];
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);  1<<(j-1)
        }
    }
}
int query(int l,int r){    o(1)
    int k=log2(r-l+1);
    return max(f[l][k],f[r-(1<<k)+1][k]);
}
法二:ST表,比单调栈麻烦,ST表预处理的是每个区间最大值,然后枚举i,二分区间右端点,进行o(1)查询[i+1,mid]是否合法
题目理解为每头牛向右看,有多少个比自己矮的,遇到第一个>=自己的就不算了.
P2866 [USACO06NOV] Bad Hair Day S
https://www.luogu.com.cn/problem/P2866
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i];
    init();
    int ans=0;
    for(int i=1;i<=n-1;i++){   o(nlogn)
        int l=i+1,r=n,res=i;
        while(l<=r){
            int mid=(l+r)>>1;
            if(query(i+1,mid)<arr[i]){
                res=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        ans+=res-i;
    }
    cout<<ans;
}

P7167 [eJOI2020 Day1] Fountain - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路: 标签:倍增,ST表,应该再加个二分标签。

ST表用作二分找第i个盘的下一个盘,然后建图 建图之后倍增。

倍增的过程跟lca的初始化过程同理。

int n,q;
pair<int,int> arr[100005];
int mx[100005][25];
int nex[100005];
void init1(){
    for(int i=1;i<=n;i++) mx[i][0]=arr[i].first;
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r){
    int k=log2(r-l+1);
    return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
pair<int,int> f[100005][25];   f[i][j]定义为,从第i个点往下跳2^j步,为<累计水容量,到达的层数编号>
void init2(){           往下跳--倍增
    for(int i=1;i<=n;i++) f[i][0]={arr[i].second+arr[nex[i]].second,nex[i]};
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            int halfIdx=f[i][j-1].second;
            f[i][j].first=f[i][j-1].first+f[halfIdx][j-1].first-arr[halfIdx].second;
            f[i][j].second=f[halfIdx][j-1].second;
        }
    }
}
标签:倍增,ST表,,应该再加个二分标签
ST表用作二分找第i个盘的下一个盘,然后建图
建图之后倍增,跟lca的初始化过程同理
基本上是一发入魂(最后输出答案的地方判错了一点,导致全wa)!!无敌了,第一次写倍增。
P7167 [eJOI2020 Day1] Fountain
https://www.luogu.com.cn/problem/P7167
void solve(){                   好题,好题.倍增入门题.
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>arr[i].first>>arr[i].second;   <直径,容量>
    init1();
    for(int i=1;i<=n;i++){    o(nlogn)  n次二分,每次check是o(1)的
        int l=i+1,r=n,res=0;
        while(l<=r){
            int mid=(l+r)>>1;
            if(query(i+1,mid)>arr[i].first){
                res=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        nex[i]=res;   建图
    }
    init2();
    while(q--){
        int x,v; cin>>x>>v;
        if(v<=arr[x].second) cout<<x<<endl;
        else{
            int cur=x;
            j从大到小
            for(int j=20;j>=0;j--){
                if(f[cur][j].second!=0&&f[cur][j].first<=v){
                    v-=f[cur][j].first;
                    cur=f[cur][j].second;
                    v+=arr[cur].second;
                }
            }
            if(v<=arr[cur].second) cout<<cur<<endl; v<=arr[cur].second 而不是v==0
            else cout<<nex[cur]<<endl;
        }
    }
}

  P7809 [JRKSJ R2] 01 序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

预处理0/1个数的前缀和pre1
第一个ST表f0,f1记录区间第一个/最后一个0/1的位置
对于询问二,查询第一个0的位置和最后一个1的位置即可--空间不够
对于询问一,分两种情况,假设以1开头,那么计算区间1的个数即可
假设以0开头,那么还需要一个前缀和pre2,其中0为-1,1为1。  可以只用一个变量,优化1e6的空间
那么通过pre2再预处理出ST表mi,维护区间最小值--用作取0的第一个位置
第一发55分,MLE了好几个点,其他都是ac,成功一半! 但是MLE怎么优化??关了longlong还是MLE
的确存了太多东西了.n=1e6的时候就MLE
优化的点:第二问的答案可以只依靠于1e6的空间
记得关long long
int n,q;
int arr[1000006];
pair<int,int> pre1[1000006];     <0的前缀和,1的前缀和>
//int f0[1000006][21];             区间中,第一个0的位置--用来处理第二问,这样很浪费空间。
int newF0[1000006];     用来处理第一问,当出现01这样的连续对才++.也是一个前缀和,这个是看题解的.
int f1[1000006][21];   区间中第一个1的位置--用来处理第一问
int pre2[1000006];
int mi[1000006][21];  区间中最小值的位置--用来处理第一问
void init(){
    for(int i=1;i<=n;i++){
        if(arr[i]) f1[i][0]=i;
        else f1[i][0]=0;
    }
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            f1[i][j]=min(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);
            pre2[mi[i][j-1]]<=pre2[mi[i+(1<<(j-1))][j-1]]?mi[i][j]=mi[i][j-1]:mi[i][j]=mi[i+(1<<(j-1))][j-1];
        }
    }
}
inline int query1(int l,int r){
    int k=log2(r-l+1);
    return min(f1[l][k],f1[r-(1<<k)+1][k]);
}
inline int querymi(int l,int r){    求得取0的位置,后面全取1
    int k=log2(r-l+1);
    if(pre2[mi[l][k]]<=pre2[mi[r-(1<<k)+1][k]]) return mi[l][k];
    return mi[r-(1<<k)+1][k];
}
预处理0/1个数的前缀和pre1
第一个ST表f0,f1记录区间第一个/最后一个0/1的位置
对于询问二,查询第一个0的位置和最后一个1的位置即可--空间不够
对于询问一,分两种情况,假设以1开头,那么计算区间1的个数即可
假设以0开头,那么还需要一个前缀和pre2,其中0为-1,1为1。  可以只用一个变量,优化1e6的空间
那么通过pre2再预处理出ST表mi,维护区间最小值--用作取0的第一个位置
P7809 [JRKSJ R2] 01 序列
https://www.luogu.com.cn/problem/P7809
第一发55分,MLE了好几个点,其他都是ac,成功一半! 但是MLE怎么优化??关了longlong还是MLE
的确存了太多东西了.n=1e6的时候就MLE
优化的点:第二问的答案可以只依靠于1e6的空间
记得关long long
void solve(){
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>arr[i];
        newF0[i]=newF0[i-1];
        if(arr[i]){
            pre1[i].first=pre1[i-1].first;
            pre1[i].second=pre1[i-1].second+1;
            pre2[i]=pre2[i-1]+1,mi[i][0]=i;
            if(i!=1&&arr[i-1]==0) newF0[i]++;
        }
        else{
            pre1[i].first=pre1[i-1].first+1;
            pre1[i].second=pre1[i-1].second;
            pre2[i]=pre2[i-1]-1,mi[i][0]=i;
        }
    }
    init();
    while(q--){
        int op,l,r; cin>>op>>l>>r;
        if(op==1){
            int res=0,fir1=query1(l,r);
            if(fir1!=0) res=pre1[r].second-pre1[fir1-1].second;     全取1
            int idx=querymi(l,r);
            if(idx!=0) res=max(res,pre1[idx].first-pre1[l-1].first+pre1[r].second-pre1[idx-1].second);
            cout<<res<<endl;
        }
        if(op==2){        优化空间!原本需要知道第一个0的位置和最后一个1的位置.现在一个1e6的01前缀和数组即可.
            if(newF0[r]!=newF0[l]) cout<<"2"<<endl;
            else cout<<"1"<<endl;
        }
    }
}

P8773 [蓝桥杯 2022 省 A] 选数异或 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

题意:给定x,n个数字,m次区间询问,每次询问区间[l,r]中是否存在两个数字异或为x,输出yes/no.
st表应该存什么?...思维.
一个一直都没有想到的性质:a^b=x --> a^x=b     --key!!
那么对于每一个ai,可以知道其匹配的bi.
那么!可以在读入数据时标记每个ai出现的位置.
再次遍历arr,计算出bi,并且记录其出现的位置.
因为可能有重复的bi,那么对于每一个ai,记录其bi左边最大的idxl,和其右边最小的idxr,不存在则取最值--二分找
维护两个st表,mx为idxl的max值,mi为idxr的min值,这样区间查询[l,r]的时候,只需要mx或mi中其中一个的值在[l,r]中即可.
思维,st表维护的东西不能单纯看出来,需要转化一下题目需要的东西,实质,再用st表维护.
在本题是维护每一个ai,其满足条件的数字"最近的位置"在哪里,这个最近的位置可用st表维护
int n,m,x;
int arr[100005];
vector<int> vct[(1<<20)+10];
int mi[100005][25],mx[100005][25];
void init(){
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
        }
    }
}
inline int querymi(int l,int r){
    int k=log2(r-l+1);
    return min(mi[l][k],mi[r-(1<<k)+1][k]);
}
inline int querymx(int l,int r){
    int k=log2(r-l+1);
    return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
题意:给定x,n个数字,m次区间询问,每次询问区间[l,r]中是否存在两个数字异或为x,输出yes/no.
st表应该存什么?...思维.
一个一直都没有想到的性质:a^b=x --> a^x=b     --key!!
那么对于每一个ai,可以知道其匹配的bi.
那么!可以在读入数据时标记每个ai出现的位置.
再次遍历arr,计算出bi,并且记录其出现的位置.
因为可能有重复的bi,那么对于每一个ai,记录其bi左边最大的idxl,和其右边最小的idxr,不存在则取最值--二分找
维护两个st表,mx为idxl的max值,mi为idxr的min值,这样区间查询[l,r]的时候,只需要mx或mi中其中一个的值在[l,r]中即可.
思维,st表维护的东西不能单纯看出来,需要转化一下题目需要的东西,实质,再用st表维护.
在本题是维护每一个ai,其满足条件的数字"最近的位置"在哪里,这个最近的位置可用st表维护
P8773 [蓝桥杯 2022 省 A] 选数异或
https://www.luogu.com.cn/problem/P8773
void solve(){
    cin>>n>>m>>x;
    for(int i=1;i<=n;i++){
        cin>>arr[i];
        vct[arr[i]].emplace_back(i);
    }
    for(int i=1;i<=n;i++){
        int b=arr[i]^x;
        auto p=lower_bound(vct[b].begin(),vct[b].end(),i);
        if(p!=vct[b].end()) mi[i][0]=*p;            右边是mi
        if(p!=vct[b].begin()) mx[i][0]=*prev(p);    左边是mx
    }
    init();
    while(m--){
        int l,r; cin>>l>>r;
        int m1=querymi(l,r),m2=querymx(l,r);
        if(m1>=l&&m1<=r||m2>=l&&m2<=r) cout<<"yes"<<endl;
        else cout<<"no"<<endl;
    }
}

P8818 [CSP-S 2022] 策略游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:这个题相比前面两个题简单很多。分别维护两个数组的正mx/mi和负mx/mi,然后分类讨论,贪心,答案取max即可.

typedef struct node{
    int pmx,pmi,nmx,nmi;
}node;
const int N=100005;
int n,m,q;
int posmx1[N][25],posmi1[N][25],negmx1[N][25],negmi1[N][25];
int posmx2[N][25],posmi2[N][25],negmx2[N][25],negmi2[N][25];
void init(){
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            posmx1[i][j]=max(posmx1[i][j-1],posmx1[i+(1<<(j-1))][j-1]);
            posmi1[i][j]=min(posmi1[i][j-1],posmi1[i+(1<<(j-1))][j-1]);
            negmx1[i][j]=max(negmx1[i][j-1],negmx1[i+(1<<(j-1))][j-1]);
            negmi1[i][j]=min(negmi1[i][j-1],negmi1[i+(1<<(j-1))][j-1]);

            posmx2[i][j]=max(posmx2[i][j-1],posmx2[i+(1<<(j-1))][j-1]);
            posmi2[i][j]=min(posmi2[i][j-1],posmi2[i+(1<<(j-1))][j-1]);
            negmx2[i][j]=max(negmx2[i][j-1],negmx2[i+(1<<(j-1))][j-1]);
            negmi2[i][j]=min(negmi2[i][j-1],negmi2[i+(1<<(j-1))][j-1]);
        }
    }
}
inline node query1(int l,int r){
    int k=log2(r-l+1);
    node res;
    res.pmx=max(posmx1[l][k],posmx1[r-(1<<k)+1][k]);
    res.pmi=min(posmi1[l][k],posmi1[r-(1<<k)+1][k]);
    res.nmx=max(negmx1[l][k],negmx1[r-(1<<k)+1][k]);
    res.nmi=min(negmi1[l][k],negmi1[r-(1<<k)+1][k]);
    return res;
}
inline node query2(int l,int r){
    int k=log2(r-l+1);
    node res;
    res.pmx=max(posmx2[l][k],posmx2[r-(1<<k)+1][k]);
    res.pmi=min(posmi2[l][k],posmi2[r-(1<<k)+1][k]);
    res.nmx=max(negmx2[l][k],negmx2[r-(1<<k)+1][k]);
    res.nmi=min(negmi2[l][k],negmi2[r-(1<<k)+1][k]);
    return res;
}
P8818 [CSP-S 2022] 策略游戏
https://www.luogu.com.cn/problem/P8818
void solve(){
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++){
        int x; cin>>x;
        if(x>=0) posmx1[i][0]=x,posmi1[i][0]=x,negmx1[i][0]=INT_MIN,negmi1[i][0]=INT_MAX;
        else posmx1[i][0]=INT_MIN,posmi1[i][0]=INT_MAX,negmx1[i][0]=x,negmi1[i][0]=x;
    }
    for(int i=1;i<=m;i++){
        int x; cin>>x;
        if(x>=0) posmx2[i][0]=x,posmi2[i][0]=x,negmx2[i][0]=INT_MIN,negmi2[i][0]=INT_MAX;
        else posmx2[i][0]=INT_MIN,posmi2[i][0]=INT_MAX,negmx2[i][0]=x,negmi2[i][0]=x;
    }
    init();
    while(q--){
        int l1,r1,l2,r2; cin>>l1>>r1>>l2>>r2;
        node res1=query1(l1,r1),res2=query2(l2,r2);
        int ans=LONG_LONG_MIN;
        if(res2.pmx!=INT_MIN&&res2.nmi!=INT_MAX){  res2有正有负
            if(res1.pmx!=INT_MIN) ans=max(ans,res1.pmi*res2.nmi);
            if(res1.nmi!=INT_MAX) ans=max(ans,res1.nmx*res2.pmx);
        }
        else if(res2.pmx!=INT_MIN){         res2只有正
            if(res1.pmx!=INT_MIN) ans=max(ans,res1.pmx*res2.pmi);
            if(res1.nmi!=INT_MAX) ans=max(ans,res1.nmx*res2.pmx);
        }
        else if(res2.nmi!=INT_MAX){         res2只有负
            if(res1.pmx!=INT_MIN) ans=max(ans,res1.pmi*res2.nmi);
            if(res1.nmi!=INT_MAX) ans=max(ans,res1.nmi*res2.nmx);
        }
        cout<<ans<<endl;
    }
}

J-Rigged Games_2024牛客暑期多校训练营3 (nowcoder.com)

思路:通过两次二分,二分出从每一个点开始,小场结束的点在哪里,这就是每一个点往后跳的第一步。有了第一步之后,倍增预处理出f数组即可。

typedef struct node{
    int cnt0=0,cnt1=0,nex0=-1;
}node;
int n,a,b;
string str;
int pre0[200005];
int pre1[200005];
int nex[100005];
node f[100005][25];     <0胜场,1胜场>
void init(){
    for(int j=1;j<=20;j++){         最多跳1e6步,足矣. 最坏的情况是要跳2e5步
        for(int i=1;i<=n/2;i++){   这里是i<=n/2!..
            f[i][j].cnt0=f[i][j-1].cnt0+f[f[i][j-1].nex0][j-1].cnt0;
            f[i][j].cnt1=f[i][j-1].cnt1+f[f[i][j-1].nex0][j-1].cnt1;
            f[i][j].nex0=f[f[i][j-1].nex0][j-1].nex0;
        }
    }
}
倍增:只需要处理出跳一步的信息,后面都可以倍增推出跳2,4,8,16,32..步的信息,跟喷泉那题一样.
Rigged Games-操纵游戏
https://ac.nowcoder.com/acm/contest/81598/J
void solve(){
    cin>>n>>a>>b;       a是小场,b是大场
    cin>>str;
    str=" "+str+str,n*=2;  字符串重复两次
    for(int i=1;i<=n;i++){
        pre0[i]=pre0[i-1];
        pre1[i]=pre1[i-1];
        str[i]=='0'?pre0[i]++:pre1[i]++;
    }
    for(int i=1;i<=n/2;i++){     二分出每个点,第一步跳到的地方(小场比赛的结束点+1)
        int l=0,r=1e5,res0=0;        先二分循环的次数,因为当a,b很大(1e5),n很小(2)的时候,是二分不出来下标的
        while(l<=r){
            int mid=(l+r)>>1;
            if(mid*pre0[n/2]<a&&mid*pre1[n/2]<a){  这里第一发wa了因为少了<a..改了之后就过了,幸好不是什么大问题.
                res0=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        int ad1=res0*pre0[n/2],ad2=res0*pre1[n/2];
        l=i,r=n;        二分结束点的下标
        pair<int,int> res={0,0};
        while(l<=r){
            int mid=(l+r)>>1;
            int num1=pre0[mid]-pre0[i-1];
            int num2=pre1[mid]-pre1[i-1];
            if(num1+ad1>=a&&num2+ad2>=a) r=mid-1;
            else if(num1+ad1<a&&num2+ad2<a) l=mid+1;
            else if(num1+ad1>=a&&num2+ad2<a) res={mid,0},r=mid-1;
            else if(num2+ad2>=a&&num1+ad1<a) res={mid,1},r=mid-1;
        }
        res.first%=(n/2);
        nex[i]=res.first+1;
        res.second==0?f[i][0]={1,0,nex[i]}:f[i][0]={0,1,nex[i]};
    }
    init();
    for(int i=1;i<=n/2;i++){
        int cnt0=0,cnt1=0,cur=i;
        for(int j=20;j>=0;j--){
            if(f[cur][j].nex0!=-1&&(f[cur][j].cnt0+cnt0<=b&&f[cur][j].cnt1+cnt1<b||f[cur][j].cnt0+cnt0<b&&f[cur][j].cnt1+cnt1<=b)) {
                cnt0+=f[cur][j].cnt0;
                cnt1+=f[cur][j].cnt1;
                cur=f[cur][j].nex0;
            }
        }
        cnt0==b?cout<<"0":cout<<"1";
    }
}

P4155 [SCOI2015] 国旗计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

给n个区间标号.
st表f维护区间的可达最远距离,以及其标号.
通过st表,可得每一个区间可以跳到的下一个最远区间,及区间的标号.--不能用st表来完成这一步,因为l,r最大有1e9.
不能用st表的话,那么可以对arr按<l,r>从小到大进行排序,之后再二分下标,二分出当前区间的下一个可以跳到的最远的区间.(注意每个区间要重复+m一次,代表循环)
然后正常倍增初始化出数组即可.
最后再处理出答案.
typedef struct node{
    int l,r,id;
    bool operator <(const node &x) const{
        if(x.l!=l) return l<x.l;
        return r<x.r;
    }
}node;
int n,m;            n个战士的奔袭区间,m个边防站.
node arr[2*200005];
int ans[200005];
unordered_map<int,pair<int,int>> mp;
pair<int,int> f[2*200005][35];   <距离,标号>
void init(){
    for(int j=1;j<=30;j++){
        for(int i=1;i<=2*n;i++){
            f[i][j]=f[ f[i][j-1].second ][j-1];
        }
    }
}
给n个区间标号.
st表f维护区间的可达最远距离,以及其标号.
通过st表,可得每一个区间可以跳到的下一个最远区间,及区间的标号.--不能用st表来完成这一步,因为l,r最大有1e9.
不能用st表的话,那么可以对arr按<l,r>从小到大进行排序,之后再二分下标,二分出当前区间的下一个可以跳到的最远的区间.
然后正常倍增初始化出数组即可.
最后再处理出答案.
P4155 [SCOI2015] 国旗计划
https://www.luogu.com.cn/problem/P4155
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>arr[i].l>>arr[i].r;
        if(arr[i].r<arr[i].l) arr[i].r+=m;
        arr[i].id=i;
        arr[i+n].l=arr[i].l+m;
        arr[i+n].r=arr[i].r+m;
        arr[i+n].id=arr[i].id+m;
    }
    sort(arr+1,arr+2*n+1);
    for(int i=1;i<=2*n;i++) mp[i]={arr[i].l,arr[i].r};
    for(int i=1;i<=2*n;i++){
        int l=i,r=2*n;
        pair<int,int> res={0,0};
        while(l<=r){
            int mid=(l+r)>>1;
            if(mp[mid].first>=arr[i].l&&mp[mid].first<=arr[i].r){
                res.first=mp[mid].second;
                res.second=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        f[i][0]=res;
    }
    init();
    for(int i=1;i<=n;i++){
        int to=arr[i].l+m;
        if(arr[i].id<=n){
            int cur=i;
            for(int j=30;j>=0;j--){
                一开始全wa,以为错了什么地方,原来是这里ans[arr[i].id]写成了arr[i]..样例还刚刚好过了..
                if(f[cur][j].first<to) cur=f[cur][j].second,ans[arr[i].id]+=pow(2,j);
            }
            ans[arr[i].id]+=2;  本身和最后一步
            一开始全wa,以为错了什么地方,原来是这里ans[arr[i].id]写成了arr[i]..样例还刚刚好过了..
        }
    }
    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}

码题集OJ-毕业照 (matiji.net)

思路:第一问是简单的二分,第二问可以直接贪心,不用二分。第三问先维护区间最大/最小值,再二分出每一个点可以到达最远的点的下标,然后用倍增初始化出f[i][j]数组即可。要注意的是倍增时大于n的情况!!还有cur的更新是当前可达点的下一个点!细节见代码注释key.

int n,k,d;
int arr[500005];
int ans1=0,ans2=0,ans3=0;
int mx[500005][20];
int mi[500005][20];
int f[500005][20];
void init0(){
    for(int j=1;j<=19;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
            mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
        }
    }
}
inline int querymx(int l,int r){
    int k0=log2(r-l+1);
    return max(mx[l][k0],mx[r-(1<<k0)+1][k0]);
}
inline int querymi(int l,int r){
    int k0=log2(r-l+1);
    return min(mi[l][k0],mi[r-(1<<k0)+1][k0]);
}
bool check(int x){         最小的d
    int cnt=1,mx0=arr[1],mi0=arr[1];
    for(int i=1;i<=n;i++){
        if(abs(arr[i]-mx0)<=x&&abs(arr[i]-mi0)<=x) mi0=min(mi0,arr[i]),mx0=max(mx0,arr[i]);
        else cnt++,mx0=arr[i],mi0=arr[i];
    }
    return cnt<=k;
}
void init1(){
    for(int j=1;j<=19;j++){
        for(int i=1;i<=n;i++){
            f[i][j]=f[ min(n,f[i][j-1]+1) ][j-1];   这里是f[i][j-1]+1 --key1:需要+1,是后一步跳的j-1
            key2:与n取min是因为当1可以直接到达n时,会导致除了第一步,其他f全为0
        }
    }
}
不能开long long,开了long long会TLE,这题的跑的时间很极限930ms.
这题的细节要注意好!!
见代码注释4个key!
BD202429-毕业照
https://www.matiji.net/exam/brushquestion/29/4498/F16DA07A4D99E21DFFEF46BD18FF68AD
void solve(){
    cin>>n>>k>>d;
    for(int i=1;i<=n;i++) cin>>arr[i],mx[i][0]=arr[i],mi[i][0]=arr[i];
    init0();
    int l=0,r=1e9;
    while(l<=r){            第一问
        int mid=(l+r)>>1;
        if(check(mid)){
            ans1=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    ans2=1;
    int mx0=arr[1],mi0=arr[1];
    for(int i=1;i<=n;i++){      第二问
        if(abs(arr[i]-mx0)<=d&&abs(arr[i]-mi0)<=d) mi0=min(mi0,arr[i]),mx0=max(mx0,arr[i]);
        else ans2++,mx0=arr[i],mi0=arr[i];
    }
    for(int i=1;i<=n;i++){      初始化出f[i][0]
        l=i,r=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if( querymx(i,mid)-querymi(i,mid)<=d ){
                f[i][0]=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
    }
    init1();
    for(int i=1;i<=n;i++){
        int cur=i,left=k,res=0;
        for(int j=19;j>=0;j--){
            if(left>=(1<<j)&&cur<=n){    key4:cur<=n,否则当cur超出了n,而f最大的值为n,会导致res加负数
                left-=(1<<j);
                res+=f[cur][j]-cur+1;
                cur=f[cur][j]+1;        key3:这是可跳到的下一个点
            }
        }
        ans3=max(ans3,res);
    }
    cout<<ans1<<endl<<ans2<<endl<<ans3;
}

码题集OJ-粒子消消乐 (matiji.net)

思路:有点小思维的倍增.

一个应该发现,但是完全没有发现的key:在一个[l,r]中,最多只有26个不同的字母贡献答案.
并且该字母是奇数存在,并且是最后一个该字母会贡献答案,中间可以通过倍增快速跳跃.
ps:一开始以为从每个字母跳跃的复杂度会是r-l+1的,但是不可能的,因为一旦有重复的字母出现,都会消去,不可能全部遍历完[l,r]
A A B A A C A A D...A A Z A A A.. 这种样例不会跑满n次,因为BCD..Z25个字母贡献完答案之后,剩下全是A倍增跳跃得很快
要注意环的cur-n和设定无效范围4e5和初始化
时间复杂度:o(q*26*logn)
int n,q;
string str;
int arr[200005];
int pos[26];
int f[2*200005][20];
void init(){
    for(int j=1;j<=19;j++){
        for(int i=1;i<=2*n;i++){
            f[i][j]=f[ f[i][j-1]+1 ][j-1];
        }
    }
}
一个应该发现,但是完全没有发现的key:在一个[l,r]中,最多只有26个不同的字母贡献答案.
并且该字母是奇数存在,并且是最后一个该字母会贡献答案,中间可以通过倍增快速跳跃.
ps:一开始以为从每个字母跳跃的复杂度会是r-l+1的,但是不可能的,因为一旦有重复的字母出现,都会消去,不可能全部遍历完[l,r]
A A B A A C A A D...A A Z A A A.. 这种样例不会跑满n次,因为BCD..Z25个字母贡献完答案之后,剩下全是A倍增跳跃得很快
时间复杂度:o(q*26*logn)
BD202428-粒子消消乐-星耀
https://www.matiji.net/exam/brushquestion/28/4498/F16DA07A4D99E21DFFEF46BD18FF68AD
不用倍增,用前缀和+二分写一发,感觉可以,而且很好写.o(q*26*logn)
不行,二分不能处理A A B A A C A A A的情况,会把BC给覆盖掉
void solve(){
    cin>>n>>q>>str;
    for(int i=1;i<=n;i++) cin>>arr[i];
    str=" "+str+str;
    for(int i=0;i<26;i++) pos[i]=0;   init
    for(int j=19;j>=0;j--) f[400000+1][j]=4e5,f[2*n+1][j]=4e5;  init
    for(int i=2*n;i>=1;i--){
        pos[str[i]-'A']==0?f[i][0]=4e5:f[i][0]=pos[str[i]-'A'];key:设定为4e5为无效距离
        pos[str[i]-'A']=i;
    }
    init();
    while(q--){
        int op,x,y; cin>>op>>x>>y;
        if(op==1) arr[x]=y;
        else{
            if(x>y) y+=n;
            long long cur=x,ans=0;
            while(cur<=y){
                for(int j=19;j>=0;j--) if(f[cur][j]<=y) cur=f[cur][j]+1;
                if(cur<=y) cur<=n?ans+=arr[cur]:ans+=arr[cur-n],cur++;      key:注意环!!要cur-n!!
            }
            cout<<ans<<endl;
        }
    }
}

to be continue..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值