1. 程式人生 > >牛客練習賽14 B 區間連續段 倍增st表

牛客練習賽14 B 區間連續段 倍增st表

時間限制: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); } }