2014NOIP提高组D1T3 飞扬的小鸟
洛谷P1941 原题地址
这是一道DP题,也是一道终极细节题。朴素的DP方程还是比较好想的。如果用
其中
处理完了所有
DP部分代码如下:
int nump = 1;
for (int i = 1; i <= n; i++) {
int lower = 0, upper = m;
if (pipe[nump].pos == i) {
upper = pipe[nump].up - 1;
lower = pipe[nump++].bottom + 1;
}
for (int j = lower; j <= upper; j++) {
for (int k = 1; j - k * lift[i - 1] >= 0; k++) { // 上升到(i,j)
if (j == m) { // 特判m
for (int q = j - k * lift[i - 1]; q <= m ; q++)
f[i][j] = min(f[i][j], f[i - 1][q] + k);
} else {
f[i][j] = min(f[i][j], f[i - 1][j - k * lift[i - 1]] + k);
}
}
if (j + down[i - 1] <= m) f[i][j] = min(f[i][j], f[i - 1][j + down[i - 1]]); // 下降到(i,j)
}
}
但很不幸,这个代码并没有那到预期的蛙WA了。于是乎下了数据,把所有
for (int k = 1; j - k * lift[i - 1] >= 0; k++)
中,
发现了错误并进行改正后,第三版代码横空出世:
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
struct Pipe {
int pos, up, bottom; // 柱子的位置,顶的高度和底的高度
} pipe[10010];
const int MAXN = (1 << 30);
int n, m, k, lift[10010], down[10010], f[10010][1010];
bool cmp(Pipe a, Pipe b) {
return a.pos < b.pos;
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i <= n - 1; i++)
scanf("%d%d", &lift[i], &down[i]);
for (int i = 1; i <= k; i++)
scanf("%d%d%d", &pipe[i].pos, &pipe[i].bottom, &pipe[i].up);
sort(pipe + 1, pipe + k + 1, cmp); // 把柱子按列位置排序
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++)
f[i][j] = MAXN;
} // MAXN意味着到达不了(i,j)
for (int i = 1; i <= m; i++) f[0][i] = 0; // 从第0列任意位置出发
int nump = 1; // 下一个是第几个柱子
for (int i = 1; i <= n; i++) {
int lower = 1, upper = m;
if (pipe[nump].pos == i) { // 如果这列有柱子
upper = pipe[nump].up - 1;
lower = pipe[nump++].bottom + 1;
}
for (int j = lower; j <= upper; j++) {
if (j == m) { // 上升
for (int k = 1; k <= m; k++) {
f[i][j] = min(f[i][j], f[i - 1][k] + ((m - k) / lift[i - 1]) + (((m - k) % lift[i - 1] || k == m) ? 1 : 0)); // 特判m
}
} else {
for (int k = 1; j - k * lift[i - 1] >= 0; k++) {
f[i][j] = min(f[i][j], f[i - 1][j - k * lift[i - 1]] + k);
}
}
if (j + down[i - 1] <= m) f[i][j] = min(f[i][j], f[i - 1][j + down[i - 1]]); // 下降
}
int minn = MAXN;
bool flag = false;
for (int i = n; i >= 1; i--) { // 从后往前扫
for (int j = 1; j <= m; j++) {
if (f[i][j] != MAXN) {
flag = true;
minn = min(minn, f[i][j]); // 找到这一列的最小点击数
}
}
if (flag) { // 输出
if (i == n) {
printf("1\n%d", minn);
return 0;
}
else {
for (int j = k; j >= 1; j--) {
if (i >= pipe[j].pos) {
printf("0\n%d", j);
return 0;
}
}
}
}
}
printf("0\n0"); // 一个柱子也没过
return 0;
}
这段代码已经超过了预期,用朴素算法拿到了
特别要注意的是,用朴素DP的时候是可以不更新被柱子挡住的部分的,但是这个DP中需要更新下面的柱子挡住的部分(第四代代码被这个又卡了好久),因为这会影响到上面的计算,只要计算完再把柱子的部分赋回最大值就可以了。
更新完上升的情况后,正常再做一遍下降的更新就可以了。注意更新上升下降的顺序不能颠倒,因为根据我们的更新方法,颠倒顺序后可能出现先下降再上升的情况,而这是不符合题意的。
AC代码(第五代代码)如下:
#include<bits/stdc++.h>
using namespace std;
struct Pipe {
int pos, up, bottom;
} pipe[10010];
const int MAXN = (1 << 30);
int n, m, k, lift[10010], down[10010], f[10010][1010];
bool cmp(Pipe a, Pipe b) {
return a.pos < b.pos;
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i <= n - 1; i++)
scanf("%d%d", &lift[i], &down[i]);
for (int i = 1; i <= k; i++)
scanf("%d%d%d", &pipe[i].pos, &pipe[i].bottom, &pipe[i].up);
sort(pipe + 1, pipe + k + 1, cmp);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++)
f[i][j] = MAXN;
}
for (int i = 1; i <= m; i++) f[0][i] = 0;
int nump = 1;
for (int i = 1; i <= n; i++) {
int lower = 1, upper = m;
if (pipe[nump].pos == i) {
upper = pipe[nump].up - 1;
lower = pipe[nump++].bottom + 1;
}
for (int j = 1; j <= upper; j++) { // 上升
for (int k = j - lift[i - 1]; k <= m; k++) {
if (k > j - lift[i - 1] && j < m) break; // 高度不是m就只做一次退出
if (k >= 1) f[i][j] = min(f[i][j], min(f[i - 1][k], f[i][k]) + 1);
}
}
for (int j = lower; j <= upper; j++) {
if (j + down[i - 1] <= m) { // 下降
f[i][j] = min(f[i][j], f[i - 1][j + down[i - 1]]);
}
}
for (int j = 1; j <= lower - 1; j++) f[i][j] = MAXN; // 把柱子原来的最大值填回去
}
int minn = MAXN;
bool flag = false;
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= m; j++) {
if (f[i][j] != MAXN) {
flag = true;
minn = min(minn, f[i][j]);
}
}
if (flag) {
if (i == n) {
printf("1\n%d", minn);
return 0;
}
else {
for (int j = k; j >= 1; j--) {
if (i >= pipe[j].pos) {
printf("0\n%d", j);
return 0;
}
}
}
}
}
printf("0\n0");
return 0;
}