题干:

链接:​​https://ac.nowcoder.com/acm/contest/82/B​​​
 

给你一个长为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

解题报告:
首先发现可以贪心,这样是O( nm )的
由于k固定,考虑数组中每个位置i向最大的j+1使得a[i..j]的和<=k连边。这个连边的结构是个森林,每次查询即查询树的一条链,可以倍增维护。O( nlogn + mlogn )

AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define FF first
#define SS second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 1e6 + 5;
int n,m;
ll K,a[MAX],sum[MAX];
int f[MAX][22];
int main()
{
cin>>n>>m>>K;
for(int i = 1; i<=n; i++) scanf("%lld",a+i),sum[i] = sum[i-1] + a[i];
for(int i = 1; i<=n; i++) {
int pos = upper_bound(sum+i,sum+n+1,sum[i-1]+K) - sum;
f[i][0] = pos;
}
for(int j = 0 ; j<=21; j++) f[n+1][j] = n+1;
for(int j = 1; j<=21; j++) {
for(int i = 1; i<=n; i++)
f[i][j] = f[f[i][j-1]][j-1];
}
while(m--) {
int l,r;
scanf("%d%d",&l,&r);
int ans = 0;
for(int j = 21; j>=0; j--) {
if(f[l][j] <= r) ans += (1<<j),l = f[l][j];
}
if(f[l][0] > r) {
printf("%d\n",ans+1);
}
else printf("Chtholly\n");
}
return 0 ;
}