(区间 dp)洛谷 P1220 关路灯/P2466 Sue 的小球 题解
1.关路灯
题意
某一村庄在一条路线上安装了 n n n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。
为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。
现在已知老张走的速度为 1 m / s 1m/s 1m/s,每个路灯的位置(是一个整数 a i a_i ai,即距路线起点的距离,单位: m m m)、功率( W W W),老张关灯所用的时间很短而可以忽略不计。
请你为老张编一程序来安排关灯的顺序,老张从下标为 c c c 的路灯开始走(即 c c c 可以马上被关掉),使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。
1 ≤ n ≤ 50 1\le n\le50 1≤n≤50, 1 ≤ c ≤ n 1\le c\le n 1≤c≤n, 1 ≤ W i ≤ 100 1\le W_i \le 100 1≤Wi≤100。
思路
这是古早时候做的题了,当时没有写题解的意识。
其实这是一道经典的区间 dp 题。首先明确一点就是,老张遇到一个路灯就会关掉(难道眼睁睁看着继续功率损耗吗?反正关灯不消耗时间)。
可以看做关一段灯,是连续的。但是老张从哪个方向扫过去关灯呢?那么状态就要多一个维度,也就是“方向”:设 f i , j , o p f_{i,j,op} fi,j,op 表示,关掉编号在区间 [ i , j ] [i,j] [i,j] 的路灯,最终停在左( o p = 0 op=0 op=0)或右($op=1%)端点,消耗的最小功率。
设
s
u
m
l
,
r
=
∑
i
=
l
r
W
i
sum_{l,r}=\sum_{i=l}^{r}W_i
suml,r=∑i=lrWi,那么:
s
1
=
(
s
u
m
1
,
i
+
s
u
m
j
+
1
,
n
)
f
i
,
j
,
0
=
min
(
f
i
+
1
,
j
,
0
+
(
a
i
+
1
−
a
i
)
s
1
,
f
i
+
1
,
j
,
1
+
(
a
j
−
a
i
)
s
1
)
\begin{matrix} s_1=(sum_{1,i}+sum_{j+1,n})\\ f_{i,j,0}=\min(f_{i+1,j,0}+(a_{i+1}-a_i)s_1,f_{i+1,j,1}+(a_j-a_i)s_1) \end{matrix}
s1=(sum1,i+sumj+1,n)fi,j,0=min(fi+1,j,0+(ai+1−ai)s1,fi+1,j,1+(aj−ai)s1)
分别表示从
i
+
1
i+1
i+1 走到
i
i
i 和从
j
j
j 走到
i
i
i ,没有关闭的所有路灯损耗的功率(此时
i
+
1
∼
j
i+1\sim j
i+1∼j 的所有路灯全都被关闭)。
s
2
=
(
s
u
m
1
,
i
−
1
+
s
u
m
j
,
n
)
f
i
,
j
,
1
=
min
(
f
i
,
j
−
1
,
0
+
(
a
j
−
a
i
)
s
2
,
f
i
,
j
−
1
,
1
+
(
a
j
−
a
j
−
1
)
s
2
)
\begin{matrix} s_2=(sum_{1,i-1}+sum_{j,n})\\ f_{i,j,1}=\min(f_{i,j-1,0}+(a_j-a_i)s_2,f_{i,j-1,1}+(a_j-a_{j-1})s_2) \end{matrix}
s2=(sum1,i−1+sumj,n)fi,j,1=min(fi,j−1,0+(aj−ai)s2,fi,j−1,1+(aj−aj−1)s2)
答案即 min ( f 1 , n , 0 , f 1 , n , 1 ) \min(f_{1,n,0},f_{1,n,1}) min(f1,n,0,f1,n,1)。
代码参考下一题,敬请自行修改。
2.Sue 的小球
题意
Sue 的目标是要收集空中漂浮的彩蛋,Sue 将小船划到一个彩蛋的正下方,便可以在瞬间收集到这个彩蛋。然而,彩蛋有一个魅力值,这个魅力值会随着彩蛋在空中降落的时间而降低,Sue 要想得到更多的分数,必须尽量在魅力值高的时候收集这个彩蛋,且 Sue 希望收集到所有的彩蛋。
然而 Sandy 就没有 Sue 那么浪漫了,Sandy 希望得到尽可能多的分数,为了解决这个问题,他先将这个游戏抽象成了如下模型:
将大海近似的看做 x x x 轴,以 Sue 所在的初始位置作为坐标原点建立一个竖直的平面直角坐标系。
一开始空中有 n n n 个彩蛋,对于第 i i i 个彩蛋,他的初始位置用整数坐标 ( x i , y i ) (x_{i}, y_{i}) (xi,yi) 表示,游戏开始后,它匀速沿 y y y 轴负方向下落,速度为 v i v_{i} vi 单位距离/单位时间。Sue 的初始位置为 ( x 0 , 0 ) (x_{0}, 0) (x0,0),Sue 可以沿 x x x 轴的正方向或负方向移动,Sue 的移动速度是 1 1 1 单位距离/单位时间,使用秘密武器得到一个彩蛋是瞬间的,得分为当前彩蛋的 y y y 坐标的千分之一。
现在,Sue 和 Sandy 请你来帮忙,为了满足 Sue 和 Sandy 各自的目标,你决定在收集到所有彩蛋的基础上,得到的分数最高(输出分数保留三位小数)。
− 1 0 4 ≤ x i , y i , v i ≤ 1 0 4 -10^4 \leq x_{i},y_{i},v_{i} \leq 10^4 −104≤xi,yi,vi≤104, n ≤ 1000 n \leq 1000 n≤1000。
思路
其实这题和上一题相同了,只要把上一题的贡献变成损耗,然后令 s s s 等于初始所有彩蛋的魅力值,用 s s s 减去最小损耗(即 min ( f 1 , n , 0 , f 1 , n , 1 ) \min(f_{1,n,0},f_{1,n,1}) min(f1,n,0,f1,n,1)),再除以 1000 1000 1000 即可。
不过这一题给的不是下标而是具体位置,而且还没排序,还需处理一下,具体细节见代码:
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
const ll N=1002,inf=0x3f3f3f3f;
ll n,x0,s;
ll sv[N];
struct node
{
ll x,y,v;
}a[N];
bool cmp(node x,node y)
{
return x.x<y.x;
}
ll f[N][N][2];//收集区间[i,j]在左/右,的损失值
ll sum(ll a,ll b,ll c,ll d)
{
return sv[b]-sv[a-1]+sv[d]-sv[c-1];
}
int main()
{
scanf("%lld%lld",&n,&x0);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i].x);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i].y);
s+=a[i].y;
}
for(int i=1;i<=n;i++)
scanf("%lld",&a[i].v);
a[++n].x=x0;
sort(a+1,a+n+1,cmp);
memset(f,inf,sizeof(f));
for(int i=1;i<=n;i++)
{
if(a[i].x==x0)
{
f[i][i][0]=f[i][i][1]=0;
break;
}
}
for(int i=1;i<=n;i++)
sv[i]=sv[i-1]+a[i].v;
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
ll j=i+len-1;
f[i][j][0]=min(f[i][j][0],min(f[i+1][j][0]+(a[i+1].x-a[i].x)*sum(1,i,j+1,n),
f[i+1][j][1]+(a[j].x-a[i].x)*sum(1,i,j+1,n)));
f[i][j][1]=min(f[i][j][1],min(f[i][j-1][0]+(a[j].x-a[i].x)*sum(1,i-1,j,n),
f[i][j-1][1]+(a[j].x-a[j-1].x)*sum(1,i-1,j,n)));
}
}
printf("%.3lf",(dd)(s-min(f[1][n][0],f[1][n][1]))/1000.0);
return 0;
}