題目連結
題目大意
給定一個長度為 \(N\) 的序列 \(A\) 和一個常數 \(K\)
有 \(M\) 次詢問
每次詢問查詢一個區間 \([L , R]\) 內所有數最少分成多少個連續段
使得每段的和都 \(<= K\) ,若無解則輸出 "\(Chtholly\)"
解題思路
簡單回憶一下倍增求 \(LCA\) 思想:
- \(f[i][j]\) 表示以 \(i\) 為起點,往上跳 \(i + 2^j\) 步後得到的祖先
- 因為往上跳 \(2^j\) 等價於先往上跳 \(2^{j - 1}\) 步後再往上跳 \(2^{j - 1}\) 步
- 所以可得: \(f[i][j] = f[f[i][j - 1]][j - 1]\)
回到這道題:
暴力的做法即遍歷區間 \([l,r]\) ,貪心的讓每段的長度儘可能大
考慮用倍增思想優化:
定義 \(f[i][j]\) 表示:
以 \(i\) 為起點,分成 \(2 ^ j\) 個連續段後,所能到達的最遠位置的下一個位置(其中每個段的和都不超過 \(K\))
那麼不難得到: \(f[i][j] = f[f[i][j - 1]][j - 1]\) (\(f[i][0]\) 可通過二分字首和得到
然後對於每個詢問 \((L , R)\):
先判斷區間 \([L,R]\) 是否存在 \(A_i\) 使得 \(A_i > K\)
這步我們維護一個權值陣列的字首和 \(O1\) 判斷
即當 \(A_i <= K\) 時,\(sum[i] = sum[i - 1]\)
當 \(A_i > K\)時,\(sum[i] = sum[i - 1] + 1\)
當 \(sum[R] - sum[L - 1] > 0\) 則表示該區間存在 \(A_i > K\),直接輸出 \(Chtholly\)
若 \(sum[R] - sum[L - 1] = 0\) 則從高位往低位列舉 \(j\):
如果 \(f[L][j] > R\) 則表示從 \(L\) 開始劃分出 \(2^j\) 個連續段是 \(OK\) 的
但是 \(2^j\) 連續段可能太多了(題目要求劃分的連續段個數最少
所以就繼續往下列舉如果 \(f[L][j] < R\),則表示從 \(L\) 開始劃分出 \(2^j\) 個連續段是不夠的
那就先劃分出 \(2^j\) 個連續段,然後再從 \(f[L][j]\) 的位置繼續劃分
即 \(ans += 1 << j\) ,\(L = f[L][j]\)
AC_Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int f[N][22];
int n , m , k , a[N] , sum[N];
long long pre[N];
signed main()
{
cin >> n >> m >> k;
for(int i = 1 ; i <= n ; i ++)
{
cin >> a[i] , pre[i] = pre[i - 1] + a[i];
sum[i] = sum[i - 1] + (a[i] > k);
}
for(int j = 0 ; j <= 21 ; j ++) f[n + 1][j] = n + 1;
for(int j = 0 ; j <= 21 ; j ++)
{
for(int i = 1 ; i <= n ; i ++)
{
f[i][0] = upper_bound(pre + i , pre + 1 + n , k - a[i] + pre[i]) - pre;
if(!j) continue ;
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
while(m --)
{
int l , r , ans = 0;
cin >> l >> r;
if(sum[r] - sum[l - 1])
{
cout << "Chtholly\n";
continue ;
}
for(int j = 21 ; j >= 0 ; j --)
{
if(f[l][j] - 1 < r)
{
ans += 1 << j;
l = f[l][j];
}
}
cout << ans + 1 << '\n';
}
return 0;
}