HDU 1300 Pearls
给
c
c
c 个种类的珍珠,每个有需要的数量以及对应的价格,购买某种类时需要额外再加十个,可以通过购买高种类的来替代低种类的。
可以得到状态转移方程:
d
p
[
i
]
=
min
{
d
p
[
k
]
+
p
[
i
]
×
(
s
[
i
]
−
s
[
k
]
+
10
)
}
,
k
∈
[
1
,
i
−
1
]
dp[i]=\min\{dp[k]+p[i]\times(s[i]-s[k]+10)\}, k\in [1,i-1]
dp[i]=min{dp[k]+p[i]×(s[i]−s[k]+10)},k∈[1,i−1]
其中
d
p
[
i
]
dp[i]
dp[i] 表示前
i
i
i 个类别购买的最优值(最小价格),
p
[
i
]
p[i]
p[i] 表示第
i
i
i 个种类的价格,用
a
[
i
]
a[i]
a[i] 表示第
i
i
i 个种类所需的数量,
s
[
i
]
s[i]
s[i] 则为
a
[
i
]
a[i]
a[i] 的前缀和。
此状态转移方程的意思是:前
i
i
i 个类别的最优值等于前
k
k
k 个类别的最优值加上类别
k
+
1
k+1
k+1 到类别
i
i
i 都用
i
i
i 类价格购买的花费,枚举
k
k
k 取最小。
很有斜率DP的味道,不妨来推一推,首先假设
k
<
j
<
i
k<j<i
k<j<i,假如
j
j
j 优于
k
k
k 的话,那么有:
d
p
[
j
]
+
p
[
i
]
×
(
s
[
i
]
−
s
[
j
]
+
10
)
<
d
p
[
k
]
+
p
[
i
]
×
(
s
[
i
]
−
s
[
k
]
+
10
)
d
p
[
j
]
−
d
p
[
k
]
<
p
[
i
]
(
s
[
j
]
−
s
[
k
]
)
d
p
[
j
]
−
d
p
[
k
]
s
[
j
]
−
s
[
k
]
<
p
[
i
]
y
j
−
y
k
x
j
−
x
k
<
p
[
i
]
(1)
\begin{aligned} dp[j]+p[i]\times (s[i]-s[j]+10)&<dp[k]+p[i]\times(s[i]-s[k]+10)\\ dp[j]-dp[k]&<p[i](s[j]-s[k])\\ \frac{dp[j]-dp[k]}{s[j]-s[k]}&<p[i]\\ \frac{y_j-y_k}{x_j-x_k}&<p[i]\tag{1} \end{aligned}
dp[j]+p[i]×(s[i]−s[j]+10)dp[j]−dp[k]s[j]−s[k]dp[j]−dp[k]xj−xkyj−yk<dp[k]+p[i]×(s[i]−s[k]+10)<p[i](s[j]−s[k])<p[i]<p[i](1)
左边是一个斜率的形式,其中
y
j
=
d
p
[
j
]
,
x
j
=
s
[
j
]
y_j=dp[j],x_j=s[j]
yj=dp[j],xj=s[j] ,也就是说当斜率小于
p
[
i
]
p[i]
p[i] 时,
j
j
j 优于
k
k
k 。
现在再考虑,对于一个给定的
i
i
i ,假如
j
j
j 优于
k
k
k ,由于
p
[
i
+
1
]
>
p
[
i
]
p[i+1]>p[i]
p[i+1]>p[i] ,也就是说对于
i
+
1
i+1
i+1,即计算
d
p
[
i
+
1
]
dp[i+1]
dp[i+1] 时,上述不等式依然成立,
j
j
j 仍然是优于
k
k
k 的。
然后令斜率
s
j
k
=
y
j
−
y
k
x
j
−
x
k
s_{jk}=\dfrac{y_j-y_k}{x_j-x_k}
sjk=xj−xkyj−yk,现有结论:对于
k
<
j
<
i
k<j<i
k<j<i ,假如
s
i
j
≤
s
j
k
s_{ij}\le s_{jk}
sij≤sjk ,那么
j
j
j 必定不是最优解,可以舍去,原因如下:
- 首先假如 s i j < p [ i ] s_{ij}<p[i] sij<p[i],那么 i i i 优于 j j j ,故 j j j 可以舍去;
- 其次假如 s i j ≥ p [ i ] s_{ij}\ge p[i] sij≥p[i] ,那么 s j k ≥ p [ i ] s_{jk}\ge p[i] sjk≥p[i] ,则 k k k 优于 j j j 故 j j j 可以舍去。
综上,假如
s
i
j
≤
s
j
k
s_{ij}\le s_{jk}
sij≤sjk ,那么
j
j
j 必定不是最优解,可以舍去。
因此维护的最优解序列是一个斜率单增的序列,可以用单调队列进行维护。
当选择最优解给
d
p
[
i
]
dp[i]
dp[i] 时,在队列头部依次找第一个斜率不符合
(
1
)
(1)
(1) 式的点进行赋值。
详见代码:
#include<iostream>
//#define WINE
#define MAXN 110
using namespace std;
int T,c,a[MAXN],p[MAXN],s[MAXN],h,t,q[MAXN],dp[MAXN];
int up(int j,int k){
return dp[j]-dp[k];
}
int down(int j,int k){
return s[j]-s[k];
}
int getDP(int i,int k){
return dp[k]+p[i]*(s[i]-s[k]+10);
}
int main(){
#ifdef WINE
freopen("data.in","r",stdin);
#endif
scanf("%d",&T);
while(T--){
scanf("%d",&c);
for(int i=1;i<=c;i++){
scanf("%d%d",&a[i],&p[i]);
s[i]=s[i-1]+a[i];
//dp[i]=(a[i]+10)*p[i];
}
h=t=0;q[t++]=0;
for(int i=1;i<=c;i++){
while(h+1<t&&up(q[h+1],q[h])<p[i]*down(q[h+1],q[h]))
h++;
dp[i]=getDP(i,q[h]);
while(h+1<t&&up(i,q[t-1])*down(q[t-1],q[t-2])<up(q[t-1],q[t-2])*down(i,q[t-1]))
t--;
q[t++]=i;
}
printf("%d\n",dp[c]);
}
return 0;
}