链接:https://www.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
分析:刚开始以为预处理+并查集呢?怎么也没想通,看了题解理解了一会。。。
由于k和n都很大,预处理最大时间nlg,查询最大时间lgn。
lg级别的查询,线段树?怎么查呢。。。倍增是个好东西,倍增+ST表,有点区间dp的意思。
首先你应该知道什么是倍增?什么是ST表?其次怎样状态转移的?
倍增法 :7 = 2^2 + 2^1 + 2^0;(我的理解);
ST表:nlg离线预处理,O(1)离线查询,类似于线段树,只不过不能动态更新点或区间;
(如下图):n=2^a+2^b+2^c….(a > b > c…),离线处理时,用st表st[j][i] :以j为起点,经过2^i次,到达终点st[j][i];
状态转移方程:st[j][i] = st[st[j][i - 1]][i - 1];
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 1e6 + 10;
int vis[MAXN], st[MAXN][25], a[MAXN];
LL sum[MAXN];
inline void st_mul(int n) {
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];
}
}
}
int main() {
int n, m, k, l, r;
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sum[i] = a[i] + sum[i - 1];
vis[i] = vis[i - 1] + (a[i] > k ? 1 : 0);
}
for(int i = 1; i <= n; ++i) {
st[i][0] = upper_bound(sum + 1, sum + n + 1, sum[i - 1] + k) - sum;
}
st_mul(n);
while(m--) {
int ans = 1;
scanf("%d %d", &l, &r);
if(vis[r] - vis[l - 1]) puts("Chtholly"); //巧妙预处理
else {
for(int i = 20; st[l][0] <= r; i--) {
//等价于: l + 2^i - 1 <= n;
if(st[l][i] && st[l][i] <= r) {
l = st[l][i];
ans += (1 << i);
}
}
printf("%d\n", ans);
}
}
return 0;
}