牛客练习赛14 B 区间连续段 倍增st表

链接:https://www.nowcoder.com/acm/contest/82/B
来源:牛客网

时间限制:C/C++ 7秒,其他语言14秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述

给你一个长为n的序列a和一个常数k

有m次询问,每次查询一个区间[l,r]内所有数最少分成多少个连续段,使得每段的和都 <= k

如果这一次查询无解,输出”Chtholly”
输入描述:

第一行三个数n,m,k
第二行n个数表示这个序列a
之后m行,每行给出两个数l r表示一次询问

输出描述:

输出m行,每行一个整数,表示答案

示例1
输入

5 5 7
2 3 2 3 4
3 3
4 4
5 5
1 5
2 4

输出

1
1
1
2
2

备注:

对于100%的数据,1 <= n , m <= 1000000 , 1 <= ai , k <= 1000000000

解题思路:
自己没想出来,没怎么接触过ST表。 看了别人的代码才发现原来可以这么写。
首先
可以在O(nlogn)的时间内求出以每个点为起点的区间终点+1最大是多少。(其实可以O(n)的处理 不过没太大必要)
设st[i][j] 表示 以i为起点 分2^j 段 最多能到哪。
可以很容易的联想到倍增 st[i][j]=st[st[i][j-1]][j-1];
这样 就可以在O(nlogn) 的时间内处理出所需状态。
然后就可以倍增的查找 查找复杂度为O(logn) 非常优秀。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int MAX=1e6+10;
LL a[MAX];
LL per[MAX];
LL n,m,k;
int st[MAX][21];
int isok[MAX];
void getst() {
    for(int i=1;(1<for(int j=1;j<=n;j++){
            st[j][i]=st[st[j][i-1]][i-1];
            //printf("st[%d][%d]=%d\n",j,i,st[j][i]);
        }
    }
}
int main() {
    scanf("%lld %lld %lld",&n,&m,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        per[i]=per[i-1]+a[i];
        isok[i]=isok[i-1]+(a[i]>k);
    }
    for(int i=1;i<=n;i++){
        st[i][0]=upper_bound(per+1,per+1+n,per[i-1]+k)-per;
       // cout<
    }
    getst();
    int l,r;
    while(m--){
        int ans=1;
        scanf("%d %d",&l,&r);
        if(isok[r]-isok[l-1]>0){
            puts("Chtholly");
            continue;
        }else{
            for(int i=20;st[l][0]<=r;i--){
                if(st[l][i] && st[l][i]<=r){
                    l=st[l][i];
                    ans+=(1<printf("%d\n",ans);
    }
}

你可能感兴趣的:(树上倍增,栩的菜鸡笔记,动态规划)