链接: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 <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);
}
}