题干:
链接: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代码:
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 ;
}