P1941 [NOIP2014 提高组] 飞扬的小鸟
P1941 [NOIP2014 提高组] 飞扬的小鸟
题意简述
给定一个游戏界面的高和宽,其中碰到上边界将不能再上升,而碰到下边界会直接死亡。又给定了一些柱子的横坐标和上下边界,小鸟在飞行时只能从中间穿过,不能碰到上下边界。求是否能到达右边。能,则输出最少的点击次数;不能,则输出最多通过的柱子数。
给定每个位置的
思路
90tps
考虑 DP。对于每个位置,我们可以记录到达该位置需要点击的最少次数。设
f_{i,j} = \max(f_{i - 1, j - k\times up_{i - 1}} + k, f_{i - 1, j + down_{i - 1}}) 由于每个位置只有他的上一个位置转移过来,所以我们自然可以用滚动数组优化(不用也不影响)。时间复杂度
O(n^2) ,预计得分70tps (考场上感觉不少就直接跳过去做下一题了,事实上是90tps )。代码
#include<bits/stdc++.h> using namespace std; #define ll long long
inline ll read(){ ll res = 0, f = 1; char c = getchar(); while(c > '9' || c < '0'){ if(c == '-')f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ res = res 10 + c - '0'; c = getchar(); } return res f; }
const int N = 100010, M = 10010; ll up[N], down[N]; struct ii{ ll h, l; int ii; }qu[N]; ll f[3][M];
signed main(){ ll n = read(), m = read(), k = read(); for(int i = 0; i < n; i ++){ up[i] = read(), down[i] = read(); } for(int i = 1; i <= k; i ++){ ll x = read(), l = read(), h = read(); qu[x] = {h - 1, l + 1}; } for(int i = 1; i <= n; i ++){ if(qu[i]. h == 0){ qu[i]. h = m; qu[i]. l = 1; qu[i]. ii = qu[i - 1]. ii; } else{ qu[i]. ii = qu[i - 1]. ii + 1; }
}
memset(f, 0x3f, sizeof f);
for(int i = 1; i <= m; i ++){
f[0][i] = 0;
}
qu[0] = {m, 1};
for(int i = 0; i < n; i ++){
bool flag = 0;
for(int j = 1; j <= m; j ++)f[(i + 1) & 1][j] = 0x3f3f3f3f3f3f3f3f;
for(int j = qu[i]. l; j <= qu[i]. h; j ++){
if(f[i & 1][j] == 0x3f3f3f3f3f3f3f3f)continue;
if(j - down[i] >= qu[i + 1]. l && j - down[i] <= qu[i + 1]. h){
f[(i + 1) & 1][j - down[i]] = min(f[(i + 1) & 1][j - down[i]], f[i & 1][j]);
flag = 1;
}
if(qu[i + 1]. h < qu[i]. l)continue;
ll sta = (qu[i + 1]. l - j) / up[i];
if((qu[i + 1]. l - j) % up[i])sta ++;
sta = max(1ll, sta);
ll endd = (qu[i + 1]. h - j) / up[i];
if(qu[i + 1]. h == m && (qu[i + 1]. h - j) % up[i]){
endd ++;
}
for(int k = sta; k <= endd; k ++){
ll hh = min(m, j + up[i] * k);
flag = 1;
f[(i + 1) & 1][hh] = min(f[(i + 1) & 1][hh], f[i & 1][j] + k);
}
}
if(!flag){
cout << 0 << endl << qu[i]. ii;
return 0;
}
}
ll ans = 0x3f3f3f3f3f3f3f3f;
for(int j = qu[n]. l; j <= qu[n]. h; j ++){
ans = min(ans, f[n & 1][j]);
}
cout << 1 << endl;
cout << ans;
return 0;
}
### 100tps
我们观察上升和下降的规则,可以发现,上升的过程和完全背包有着惊人的相似度,而下降的过程几乎就是我们熟悉的 $01$ 背包。由此,我们可以得出一个做法:将上升和下降分开处理,分别做完全和 $01$ 背包。
完全背包(上升)的第二次以及更多的点击可以直接从
$f_{i, j - up_{i - 1}}$ 转移,只有点一次是从 $f_{i-1,j - up_{i-1}}$ 转移的(完全背包 $n^2$ 法);而 $01$ 背包是从 $f_{i - 1, j - up_{i - 1}}$ 转移的。
记得特判一下顶到最上面的情况。
### 代码
```cpp
#include<bits/stdc++.h>
#define N 10005
using namespace std;
#define ll long long
int n,m,k,x[N],y[N],low[N],high[N],f[N][2005];
bool flag[N];
inline ll read(){
ll res = 0, f = 1;
char c = getchar();
while(c > '9' || c < '0'){
if(c == '-')f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
res = res * 10 + c - '0';
c = getchar();
}
return res * f;
}
int main(){
n = read(), m = read(), k = read();
for (int i = 1; i <= n; i ++) x[i] = read(), y[i] = read();
for (int i = 1; i <= n; i ++){
high[i] = m;
low[i] = 1;
}
for (int i=1;i<=k;i++){
int xx = read(), yy = read(), zz = read();
flag[xx] = 1;
low[xx] = yy + 1;
high[xx] = zz - 1;
}
memset(f, 0x3f3f3f, sizeof(f));
for (int i = 1; i <= m; i ++) f[0][i]=0;
for (int i = 1; i <= n; i ++){
for (int j = x[i] + 1; j <= x[i] + m; j ++)
f[i][j] = min(f[i - 1][j - x[i]] + 1, f[i][j - x[i]] + 1);
for (int j = m + 1; j <= x[i] + m; j ++)
f[i][m] = min(f[i][m], f[i][j]);
for (int j = 1; j <= m - y[i]; j ++)
f[i][j] = min(f[i][j], f[i - 1][j + y[i]]);
for (int j = 1;j < low[i]; j ++)
f[i][j] = 0x3f3f3f;
for (int j = high[i] + 1; j <= m; j++)
f[i][j] = 0x3f3f3f;
}
int ans=0x3f3f3f;
for (int i = 1;i <= m; i ++) ans = min(ans, f[n][i]);
if (ans < 0x3f3f3f){
cout << 1 << endl << ans;
return 0;
}
int i, j;
for (i = n; i >= 1; i --){
for (j = 1; j <= m; j ++)
if (f[i][j] < 0x3f3f3f) break;
if (j <= m) break;
}
ans = 0;
for (j = 1;j <= i; j ++)
if (flag[j]) ans ++;
cout << 0 << endl << ans;
return 0;
}