解题思路
不想叙述关于打线段树搞这题用了一个上午的事,我哭死,最后半小时发现思路错了就X尼玛,然后在线段数的基础上改了一个暴力,还比直接暴力少了30分。
70分(LZH大佬思路):设 s u m [ i ] [ j ] sum[i][j] sum[i][j]表示再i点面基,从j到i所需代价,代价就是i~j中的最大值(RMQ瞎搞就好),预处理出这个 s u m sum sum,然后做一个前缀和,最后还是要枚举面基的地点,然后输出就是 s u m [ i ] [ y ] − s u m [ i ] [ x − 1 ] sum[i][y]-sum[i][x-1] sum[i][y]−sum[i][x−1]
100分:区间DP+RMQ优化
先70分中的做法一样,做一个RMQ预处理,注意这里要每次存区间最大值所在的位置,后面DP要用到。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示区间
[
i
,
j
]
[i,j]
[i,j]面基的最小代价,转移的话,找出区间
[
i
,
j
]
[i,j]
[i,j]中最高的山
k
k
k ,
注意区间的枚举顺序需要像区间
d
p
dp
dp 那样,先枚举区间长度,然后枚举左端点。
考虑面基地点:
如果在
k
k
k 的左边,代价为
f
[
l
]
[
k
−
1
]
+
s
∗
(
r
−
k
+
1
)
f[l][k-1]+s*(r-k+1)
f[l][k−1]+s∗(r−k+1)
如果在
k
k
k 的右边,代价为
f
[
k
+
1
]
[
r
]
+
s
∗
(
k
−
l
+
1
)
f[k+1][r]+s*(k-l+1)
f[k+1][r]+s∗(k−l+1)
在这两种方案中取一个最小值即可(因为面基地点肯定不会是在 k 处的)。
f
[
l
]
[
r
]
=
m
i
n
(
f
[
l
]
[
k
−
1
]
+
s
∗
(
r
−
k
+
1
)
,
f
[
k
+
1
]
[
r
]
+
s
∗
(
k
−
l
+
1
)
)
;
f[l][r]=min(f[l][k-1]+s*(r-k+1),f[k+1][r]+s*(k-l+1));
f[l][r]=min(f[l][k−1]+s∗(r−k+1),f[k+1][r]+s∗(k−l+1));
然后每一次询问都可以O(1)回答了。
代码
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
#define db double
using namespace std;
int n,m,l,r;
ll ans,a[2010],f[2020][2020],lg[2020];
struct c{
int x,id;
}g[2020][15];
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
g[i][0].x=a[i];
g[i][0].id=i;
f[i][i]=a[i];
}
lg[0]=-1;
for(int i=1;i<=n;i++)
lg[i]=lg[i>>1]+1;
for(int j=1;j<=lg[n];j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
g[i][j].x=max(g[i][j-1].x,g[i+(1<<j-1)][j-1].x);
if(g[i][j-1].x>g[i+(1<<j-1)][j-1].x)
g[i][j].id=g[i][j-1].id;
else g[i][j].id=g[i+(1<<j-1)][j-1].id;
}
}
for(int j=1;j<=n;j++)
{
for(int l=1;l+j-1<=n;l++)
{
int r=0,w=0,s=0,k=0;
r=l+j-1;
w=lg[r-l+1];
s=max(g[l][w].x,g[r-(1<<w)+1][w].x);
if(g[l][w].x>g[r-(1<<w)+1][w].x)
k=g[l][w].id;
else k=g[r-(1<<w)+1][w].id;
f[l][r]=min(f[l][k-1]+s*(r-k+1),f[k+1][r]+s*(k-l+1));
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
printf("%d\n",f[l][r]);
}
}