牛客練習賽14 B 區間連續段 倍增st表
阿新 • • 發佈:2019-02-02
時間限制: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 <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <list>
#include <string>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <climits>
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<<i)<=n;i++){
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<<i<<":"<<st[i][0]<<endl;
}
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<<i);
}
}
}
printf("%d\n",ans);
}
}