题目大意
给出飞扬的小鸟的一个场景,判断小鸟是否能到达终点。若能,求最少点击次数;否则,求最多能通过的水管数。
解题思路
朴素算法不难想到。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示经过 (i, j) 的最少点击次数。
对于下降,
f
[
i
]
[
j
]
=
min
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
j
+
y
[
i
]
]
)
f[i][j]=\min(f[i][j],f[i-1][j+y[i]])
f[i][j]=min(f[i][j],f[i−1][j+y[i]])
对于上升,
f
[
i
]
[
j
]
=
min
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
j
−
x
[
i
]
∗
k
]
+
k
)
,
k
≥
1
f[i][j]=\min(f[i][j],f[i-1][j-x[i]*k]+k),k≥1
f[i][j]=min(f[i][j],f[i−1][j−x[i]∗k]+k),k≥1
时间复杂度
O
(
n
m
2
)
O(nm^2)
O(nm2),期望得分70。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
const int MAXN=10010;
const int MAXM=1010;
int n, m, k, s;
int x[MAXN], y[MAXN];
int l[MAXN], h[MAXN];
int f[MAXN][MAXM];
inline int read (){
int x=0; char c;
do c=getchar (); while ('0'>c||'9'<c);
while ('0'<=c&&'9'>=c)
x=x*10+c-48, c=getchar ();
return x;
}
int fail (){
int sum=0, cnt=0;
for (int i=n; 0<=i; --i){
for (int j=0; j<=m; ++j)
if (f[i][j]<1e9){
sum=i;
break;
}
if (sum) break;
}
for (int i=0; i<=sum; ++i)
if (-1!=l[i]&&m+1!=h[i])
++cnt;
return cnt;
}
int main (){
freopen ("1.in", "r", stdin);
n=read (); m=read (); k=read ();
for (int i=0; i<n; ++i){
x[i]=read (); y[i]=read ();
l[i]=-1; h[i]=m+1;
}
for (int i=1; i<=k; ++i){
s=read ();
l[s]=read (); h[s]=read ();
}
memset (f, 63, sizeof (f)); l[n]=-1; h[n]=m+1;
for (int i=0; i<=m; ++i) f[0][i]=0;
for (int i=0; i<n; ++i)
for (int j=l[i]+1; j<h[i]; ++j){
if (f[i][j]>1e9) continue;
if (j>y[i]&&l[i+1]<j-y[i]&&j-y[i]<h[i+1])
f[i+1][j-y[i]]=min (f[i+1][j-y[i]], f[i][j]);
for (int k=max (1, (l[i+1]+1-j)/x[i]); ; ++k){
int ht=min (j+x[i]*k, m);
if (l[i+1]<ht&&ht<h[i+1])
f[i+1][ht]=min (f[i+1][ht], f[i][j]+k);
if (ht==m||ht>=h[i+1]) break;
}
}
// for (int i=m; 0<=i; --i){
// for (int j=0; j<=n; ++j)
// printf ("%4d", f[j][i]>1e9?-1:f[j][i]);
// puts ("");
// }
int ans=0x3f3f3f3f;
for (int i=0; i<=m; ++i)
ans=min (ans, f[n][i]);
if (ans<1e9)
printf ("1\n%d", ans);
else{
printf ("0\n%d", fail ());
}
}
考虑如何优化时间复杂度。
注意到算法处理上升时进行了大量冗余的更新,考虑优化它。
一个位置
f
[
i
]
[
j
]
f[i][j]
f[i][j],他能被 (i-1, j-x[i-1]) 更新,也能被 (i-1, j-x[i-2]) 更新……如果使用朴素算法更新,每个
f
[
i
]
[
j
]
f[i][j]
f[i][j] 都需要访问
m
m
m 个位置 j-x[i-1], j-x[i-2], …来更新。因为我们要更新
m
m
m 个位置
f
[
i
]
[
0
]
,
f
[
i
]
[
1
]
,
.
.
.
,
f
[
i
]
[
m
]
f[i][0],f[i][1],...,f[i][m]
f[i][0],f[i][1],...,f[i][m],所以 j-x[i-1], j-x[i-2], … 被多次访问,造成时间浪费。
优化这种 m×m 的更新方式为 “2m 型”。考虑建立中继体系(虚点)降低复杂度。
设
g
[
i
]
[
j
]
g[i][j]
g[i][j] 表示从
f
[
i
−
1
]
f[i-1]
f[i−1] 跳跃到 (i, j) 的累计最小点击数。这样
g
[
i
]
[
j
]
g[i][j]
g[i][j] 的大部分都可以只令
k
=
1
k=1
k=1 完成更新。时间复杂度
O
(
m
)
O(m)
O(m)。
再用
g
[
i
]
[
j
]
g[i][j]
g[i][j] 更新
f
[
i
]
[
j
]
f[i][j]
f[i][j]。用
g
[
i
]
[
j
−
x
[
i
−
1
]
g[i][j-x[i-1]
g[i][j−x[i−1] 更新
g
[
i
]
[
j
]
g[i][j]
g[i][j] 即可。时间复杂度
O
(
m
)
O(m)
O(m)。
优化后,总时间复杂度 O ( n m ) O(nm) O(nm)。