题意:
给你一个长 n 的序列,m 次查询
每次查询给一个 x,然后:
从序列的最左端 1 开始,每次随机的选择一个右端点 r,如果两个端点间的区间和不超过 x ,就进行一次分割,然后把左端点变成 r + 1, 否则一直随机下去。
问这样分割出来的期望段数
思路:
我们试着逆向考虑这个问题。
设 d p [ i ] dp[i] dp[i]为以 i i i开始到 n n n这一段进行分割的期望段数。
显然 d p [ n ] = 1 dp[n] = 1 dp[n]=1 (因为无法再继续分割)。
对于一个已知的询问
x
x
x。
假设
j
=
m
a
x
(
k
∣
∑
t
=
i
k
a
[
t
]
<
=
x
)
j = max(k \ | \ \sum_{t=i}^{k}a[t] <= x )
j=max(k ∣ t=i∑ka[t]<=x)
则第一个分割点将在
i
−
j
i-j
i−j的范围内等可能选取。
假设分割点选在
k
k
k,则
i
−
k
i-k
i−k视为一个完整的段,其对于后面
(
k
+
1
)
−
n
(k+1)-n
(k+1)−n这一段的分割结果不产生影响,即
d
p
[
k
+
1
]
dp[k+1]
dp[k+1],只不过因为整个过程都多了一个已经分割好的段(
i
−
k
i-k
i−k),故此时的期望为
d
p
[
k
+
1
]
+
1
dp[k+1]+1
dp[k+1]+1。
故可推出:设
l
e
n
=
j
−
i
+
1
len = j-i+1
len=j−i+1
d
p
[
i
]
=
1
l
e
n
∑
t
=
i
j
(
d
p
[
t
+
1
]
+
1
)
=
1
+
∑
t
=
i
j
d
p
[
t
+
1
]
dp[i] = \frac{1}{len}\sum_{t=i}^{j}(dp[t+1] + 1) = 1 + \sum_{t=i}^{j}dp[t+1]
dp[i]=len1t=i∑j(dp[t+1]+1)=1+t=i∑jdp[t+1]
对于一个特定的
i
i
i,
j
j
j的值可以用双指针的思想进行维护。
而求和式明显可以处理前缀和,用差分的思想进行优化。
故此题可解。
代码:
#include<cstdio>
#include<cmath>
#include<set>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e5 + 10;
double dp[A],sum[A];
int n,m,Mx,a[A];
void calc(int x){
dp[n+1] = sum[n+1] = sum[n+2] = 0;
dp[n] = sum[n] = 1;
int res = a[n],ed = n;
for(int i=n-1 ;i>=1 ;i--){
res += a[i];
while(res > x){
res -= a[ed];
ed--;
}
dp[i] = 1.0 + (sum[i+1]-sum[ed+2])/(1.0*(ed-i+1));
sum[i] = sum[i+1] + dp[i];
}
}
int main(){
scanf("%d%d",&n,&m);
Mx = 0;
for(int i=1 ;i<=n ;i++){
scanf("%d",&a[i]);
Mx = max(Mx,a[i]);
}
while(m--){
int x;
scanf("%d",&x);
if(x<Mx) puts("YNOI is good OI!");
else{
calc(x);
printf("%.2f\n",dp[1]);
}
}
return 0;
}