[DP 倍增]区间的连续段
题目
思路
题意:
给你长度为n的数组,由m次查询,每次查询问对于区间[l,r],最少把区间内数切成几段,使得每一段满足和都<=k。
个人学习记录使用,参考这里的第一篇题解
数据范围很大,暴力必T
对于100%的数据,1 <= n , m <= 1000000 , 1 <= ai , k <= 1000000000
然后照着题解学了一下倍增优化
1预处理除前缀和
p
r
e
[
i
]
=
p
r
e
[
i
−
1
]
+
a
[
i
]
pre[i]=pre[i-1]+a[i]
pre[i]=pre[i−1]+a[i]
2然后对倍增DP数组做预处理
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示从i开始分成
2
j
2^j
2j段,能走到最远的位置的下一个位置
①
f
[
n
+
1
]
[
i
]
=
n
+
1
f[n+1][i]=n+1
f[n+1][i]=n+1
②
f
[
i
]
[
0
]
f[i][0]
f[i][0]就是第i个位置能走到最远的位置,这个可以用二分upper_bound预处理出满足条件
p
r
e
[
x
]
−
p
r
e
[
i
]
<
=
k
pre[x]-pre[i]<=k
pre[x]−pre[i]<=k最后一个位置的下一个位置,这个可以转化为查找第一个
>
p
r
e
[
i
−
1
]
+
k
>pre[i-1]+k
>pre[i−1]+k的位置
3倍增DP状态转移
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
[
j
−
1
]
]
[
j
−
1
]
f[i][j]=f[f[i][[j-1]][j-1]
f[i][j]=f[f[i][[j−1]][j−1]
4查询
反向遍历j
如果从i开始分成
2
j
2^j
2j段能到达的最远位置
如果比r大,减小i
如果<=r,增加答案,更新左端点
5检验
如果查询到最后
f
[
l
]
[
0
]
>
r
f[l][0]>r
f[l][0]>r表示还有答案的,因为要到f[l][0]才会>k啊
如果
f
[
l
]
[
0
]
<
=
r
f[l][0]<=r
f[l][0]<=r表示没有答案,注意这里包含<=,因为定义的是满足条件的下一个位置
代码
// Problem: 区间的连续段
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/problem/15429
// Memory Limit: 524288 MB
// Time Limit: 14000 ms
// FishingRod
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long LL;
typedef pair<int,int> PII;
//#define MULINPUT
/*DATA & KEY
n 1 1e6
m 1 1e6
ai 1 1e9
k 1 1e9
*/
int T;
LL n,m,k;
const int N=1e6+10;
LL a[N],pre[N],f[N][22];
void solve(int C)
{
//NEW DATA CLEAN
//NOTE!!!
scanf("%lld %lld %lld",&n,&m,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
pre[i]=pre[i-1]+a[i];//处理前缀和
}
for(int i=0;i<=21;i++)f[n+1][i]=n+1;//第一个预处理
for(int i=n;i>=1;i--)
{
f[i][0]=upper_bound(pre+i,pre+n+1,pre[i-1]+k)-pre;//第二个预处理
for(int j=1;j<=21;j++)
f[i][j]=f[f[i][j-1]][j-1];
}
while(m--)
{
int l,r,ans=0;
scanf("%d %d",&l,&r);
for(int i=21;i>=0;i--)
{
if(f[l][i]<=r)ans+=1<<i,l=f[l][i];
}
if(f[l][0]>r)printf("%d\n",ans+1);
else printf("Chtholly\n");
}
}
int main()
{
#ifdef MULINPUT
scanf("%d",&T);
for(int i=1;i<=T;i++)solve(i);
#else
solve(1);
#endif
return 0;
}