题目是一个无限背包+01背包,但是要先做无限背包,再做01背包。如果先做01背包,那么在做无限背包的时候会出现多次下降的情况。
根据题目,很容易想出设$f[i][j]$为在$x=i, h=j$时的最小点击次数,那么有下列方程(无限背包部分, 即上升部分)
$$f[i][j] = min(f[i][j], f[i-1][j-kx]+1)$$
很明显,这样的做法TLE。
我们回想无限背包的式子,$f[v] = max(f[v], f[v-w[i]]+c[i])$对吧?应该大部分人都记得这个“滚动后的式子”,而原式都不记得了,原式为设$f[i][v]$为前$i$个物品装进$v$容量的最大利益。则状态转移方程为
$$f[i][v] = max(f[i-1][v], f[i][v-w[i]]+c[i])$$
这个方程是”继承$f[i-1]$的值“或者”在$f[i]$中已经计算过的值再用来转移(即实现了多重)”,我们的原方程是不是也可以这样呢?答案是肯定的。
原方程可化为
$$f[i][j] = min(f[i][j-x]+1, f[i][j-x]+1)$$
这样就不会超时了,处理之后的01背包,直接在多重处理完后扫一遍,$f[i][j] = min(f[i][j], f[i-1][j+y])$就能算出来了。
注意此题要因为上升高度大于$m$则为$m$,需要进行特殊判定,并且每个是管道的状态也要转移,因为这个对多重做出了贡献,在处理完之后再进行覆盖最大值即可,否则这里会丢掉25分。直接沦为暴力选手
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ms(i, j) memset(i, j, sizeof i)
using namespace std;
const int MAXN = 10000 + 5, MAXM = 1000 + 5, INF = 1000000000;
int n, m, k, f[MAXN][MAXM], d[MAXN], u[MAXN], l[MAXN], h[MAXN];
void clean() {
for (int i=0;i<=n;i++) {
l[i] = -1;
h[i] = m + 1;
f[i][0] = INF + 1;
for (int j=1;j<=m;j++) {
if (i==0) f[i][j] = 0; else f[i][j] = INF;
}
}
}
void init() {
clean();
for (int i=1;i<=n;i++) {
scanf("%d%d", &u[i], &d[i]);
}
for (int i=1;i<=k;i++) {
int p, x, y;
scanf("%d%d%d", &p, &x, &y);
l[p] = x, h[p] = y;
}
}
void solve() {
int zz = 0, ans = INF;
for (int i=1;i<=n;i++) {
for (int j=1;j<=m;j++) {
if (j-u[i]>0) {
f[i][j] = min(f[i][j], f[i][j-u[i]]+1);
f[i][j] = min(f[i][j], f[i-1][j-u[i]]+1);
}
if (j==m) {
for (int o=0;o<u[i];o++) {
f[i][j] = min(f[i][j], f[i][m-o]+1);
f[i][j] = min(f[i][j], f[i-1][m-o]+1);
}
}
}
for (int j=1;j<=m;j++) {
if (j+d[i]<=m) f[i][j] = min(f[i][j], f[i-1][j+d[i]]);
}
for (int j=1;j<=l[i];j++) f[i][j] = INF;
for (int j=h[i];j<=m;j++) f[i][j] = INF;
}
for (int i=1;i<=n;i++) {
int ap = INF, flag = 0;
for (int j=1;j<=m;j++) {
if (f[i][j]<INF) flag = 1;
ap = min(ap, f[i][j]);
}
if (!flag) {printf("0\n%d\n", zz); return ;} else ans = ap;
if (l[i]!=-1) zz++;
}
printf("1\n%d\n", ans);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
#endif
while (scanf("%d%d%d", &n ,&m, &k)==3) init(), solve();
return 0;
}
2018.10.2,注意检查 DP 转移的完全性
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<stack>
#include<set>
#include<queue>
#include<climits>
#include<cmath>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
using namespace std;
namespace flyinthesky {
const int MAXN = 10000 + 5, MAXM = 1000 + 5, INF = 1000000000;
struct data {int l, h;} pp[MAXN];
int n, m, k, dp[MAXN][MAXM], x[MAXN], y[MAXN], vis[MAXN][MAXN];
void clean() {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) dp[i][j] = INF, vis[i][j] = 0;
for (int i = 0; i <= n; i++) pp[i] = (data){0, m + 1};
}
int solve() {
scanf("%d%d%d", &n, &m, &k);
clean();
for (int i = 0; i < n; i++) scanf("%d%d", &x[i], &y[i]);
for (int pos, i = 1; i <= k; i++) {
scanf("%d", &pos);
scanf("%d%d", &pp[pos].l, &pp[pos].h);
for (int j = 0; j <= pp[pos].l; j++) vis[pos][j] = 1;
for (int j = pp[pos].h; j <= m; j++) vis[pos][j] = 1;
}
for (int i = 1; i <= m; i++) dp[0][i] = 0;
for (int i = 1; i <= n; i++) {
for (int j = x[i - 1]; j <= m; j++) {
dp[i][j] = min(dp[i][j], dp[i][j - x[i - 1]] + 1);
if (!vis[i - 1][j - x[i - 1]]) dp[i][j] = min(dp[i][j], dp[i - 1][j - x[i - 1]] + 1);
// printf("i=%d, j=%d, dp=%d\n", i, j, dp[i][j]);
}
for (int j = m; j >= max(m - x[i - 1], 1); j--) {
dp[i][m] = min(dp[i][m], dp[i][j] + 1);//不要忘了
if (!vis[i - 1][j]) dp[i][m] = min(dp[i][m], dp[i - 1][j] + 1);
}
for (int j = 1; j + y[i - 1] <= m; j++) if (!vis[i - 1][j + y[i - 1]]) dp[i][j] = min(dp[i][j], dp[i - 1][j + y[i - 1]]);
}
/*for (int j = m; j >= 0; j--)
for (int i = 0; i <= n; i++) printf("%d%c", vis[i][j], i == n ? '\n' : ' ');
puts("");
for (int j = m; j >= 0; j--)
for (int i = 0; i <= n; i++) printf("%c%c", dp[i][j] == INF ? 'u' : dp[i][j] + '0', i == n ? '\n' : ' ');*/
int tot = 0;
for (int i = 1; i <= n; i++) {
int st = pp[i].l + 1, ed = pp[i].h - 1;
if (!(st == 1 && ed == m)) ++tot;
int fl = 0;
for (int j = st; j <= ed; j++) if (dp[i][j] != INF) {fl = 1; break;}
if (fl) continue;
return printf("0\n%d\n", tot - 1);
}
int ans = INF;
for (int j = 1; j <= m; j++) ans = min(ans, dp[n][j]);
printf("1\n%d\n", ans);
return 0;
}
}
int main() {
flyinthesky::solve();
return 0;
}