0%

「USACO18DEC」Balance Beam P & AGC044E Random Pawn

这两道题是一个类型的,所以放到一起写了。

Balance Beam P

我们不算那个什么关键点了,直接设 \(E_i\) 为最优策略下从 \(i\) 开始的报酬。那么我们有 \(E_0=E_{n+1}=0\),同时考虑这一步的决策,如果走,那么期望为 \(\dfrac{E_{i-1}+E_{i+1}}{2}\),如果不走,报酬是 \(f_i\)

所以我们有 \[ E_{i}=\max(\frac{E_{i-1}+E_{i+1}}{2},f_i)\quad (1\leqslant i\leqslant n) \] 我们有以下定论:点集 \(\{(i,E_i)\}\) 在点集 \(\{(i,f_i)\}\cup\{(0,0),(n+1,0)\}\) 所构成的凸包上。

我们不难发现 \(E_i\) 等于 \((i,f_i)\) 和经过 \((i-1,E_{i-1})\)\((i+1,E_{i+1})\) 两点的直线在 \(x=i\) 出的纵坐标的较大值,则其一定是凸的;同时不难发现,\(\{(i,f_i)\}\cup\{(0,0),(n+1,0)\}\) 构成的凸包一定满足上述条件。

下面我们证明这样的点集是唯一的。为此我们扩展一下状态:给定平面中 \(n\) 个点 \((x_i,y_i)\) 和点 \((x_0,f_0),(x_{n+1},f_{n+1})\),满足 \(x_i\) 互不相同且从小到大排列。设\(f_i=\max(y_i,\dfrac{x_{i+1}-x_i}{x_{i+1}-x_{i-1}}f_{i-1}+\dfrac{x_i-x_{i-1}}{x_{i+1}-x_{i-1}}f_{i+1})\),从图形意义上依旧是相邻的两个点形成直线和给定点的较高点。则我们只需要证明 \(\{(x_i,f_i)\}\) 的每一个顶点都是给定点集中的点。我们考虑归纳证明。

\(n=1\) 时显然成立。

\(n>1\) 时,考虑任意添加最后一个点,如果其在前 \(n+1\) 个点形成的凸包内部,则继续;反之新形成的凸包一定由原凸包上点的子集和新加入的点组成。这样一定不会有不属于原点集的点。

所以我们只需要维护原点集的凸包,这个可以线性实现。

代码(这题卡精度最后输出的时候不要用 double 要用 __int128):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
ll a[200005];
int q[200005],tl;
double f[200005];
ll crs(int x,int y,int z){return ((y-x)*(a[y]-a[z])-(y-z)*(a[y]-a[x]));}
void add(int x)
{
while(tl>1&&crs(q[tl-1],q[tl],x)<=0)tl--;
q[++tl]=x;
}
void write(__int128 x)
{
if(x<=9){putchar('0'+x);return;}
write(x/10);
putchar('0'+x%10);
}
int main()
{
int n;
scanf("%d",&n);
q[tl=1]=0;
for(int i=1;i<=n;i++)
{
long long x;
scanf("%lld",&x);
a[i]=x;
a[i]*=100000;
add(i);
}
add(n+1);
int now=1;
for(int i=1;i<=n;i++)
{
while(q[now+1]<=i)now++;
write(((((q[now+1]-i)*a[q[now]])+(i-q[now])*a[q[now+1]])/(q[now+1]-q[now])));
puts("");
}
return 0;
}

Random Pawn

上一道题的加强版。

首先我们发现,如果走到了环上的最大值,则一定会停止,所以我们可以在最大值处破环为链(令 \(0\)\(n\) 都为最大值)。

\(E_i\) 为从 \(i\) 开始的期望,那么我们现在也可以写出转移: \[ E_i=\max(A_i,\frac{E_{i-1}+E_{i+1}}{2}-B_i) \] 如果没有 \(B_i\),那就是上一道题了,我们现在考虑把 \(B_i\) 消掉。

我们尝试构造一个数列 \(\{C_i\}\),那么有 \[ E_i-C_i=\max(A_i-C_i,\frac{E_{i-1}-C_{i-1}+E_{i+1}-C_{i+1}}{2}+\frac{C_{i-1}+C_{i+1}}{2}-B_i-C_i) \] 如果我们的 \({C_n}\) 能让 \(\dfrac{C_{i-1}+C_{i+1}}{2}-B_i-C_i=0\),设 \(F_i=E_i-C_i,G_i=A_i-C_i\),则有 \[ F_i=\max(G_i,\frac{F_{i-1}+F_{i+1}}{2}) \] 这个就是上一道题的形式了,\(C_i\) 可以随便确定 \(C_0,C_1\) 然后直接递推得到(\(C_i=2C_{i-1}+2B_{i-1}-C_{i-2}\))。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll tmp1[200005],tmp2[200005],a[200005],b[200005],c[200005],g[200005];
int q[200005],tl;
ll crs(int x,int y,int z){return ((y-x)*(g[y]-g[z])-(y-z)*(g[y]-g[x]));}
void add(int x)
{
while(tl>1&&crs(q[tl-1],q[tl],x)<=0)tl--;
q[++tl]=x;
}
int main()
{
int n,mx=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&tmp1[i]);
if(tmp1[i]>tmp1[mx])mx=i;
}
for(int i=1;i<=n;i++)scanf("%lld",&tmp2[i]);
for(int i=mx;i<=n;i++)a[i-mx]=tmp1[i],b[i-mx]=tmp2[i];
for(int i=1;i<=mx;i++)a[n-mx+i]=tmp1[i],b[n-mx+i]=tmp2[i];
for(int i=2;i<=n;i++)c[i]=2*c[i-1]+2*b[i-1]-c[i-2];
for(int i=0;i<=n;i++)g[i]=a[i]-c[i],add(i);
int now=1;
double res=0;
for(int i=0;i<n;i++)
{
while(q[now+1]<=i)now++;
res+=((((q[now+1]-i)*g[q[now]])+(i-q[now])*g[q[now+1]])*1.0/(q[now+1]-q[now]))+c[i];
}
printf("%.12lf",res/n);
return 0;
}