四维dp 或者 剪枝 + dfs Codeforces Beta Round #6 (Div. 2 Only) D
http://codeforces.com/contest/6/problem/D
题目大意:有一队人,排成一列,每个人都有生命值,你每次可以攻击2~n位置的一个的人,假设每次攻击的位置为pos,那么pos位受到a点伤害,pos-1和pos+1受到b点伤害。问让所有人生命值都小于0所需要的最少操作数。
思路:最近在加强dp类型的题目,但是因为做过一点IDA*,感觉这题貌似可以用IDA*差不多的思路来做哈?(关键是我不会DP,TAT)然后我们找寻一下IDA*的可行性剪枝就好了:目前数列的maxval<=(lim - deep) * a即可,否则TLE了
这是IDA*的代码
//看看会不会爆int!数组会不会少了一维! //取物问题一定要小心先手胜利的条件 #include <bits/stdc++.h> using namespace std; #define LL long long #define ALL(a) a.begin(), a.end() #define pb push_back #define mk make_pair #define fi first #define se second #define haha printf("haha\n") const int maxn = 15; const int inf = 0x3f3f3f3f; int road[maxn * maxn]; int n, a, b; int h[maxn]; bool dfs(int pos, int deep, int lim){ int maxcnt = -inf; for (int i = 1; i <= n; i++) maxcnt = max(maxcnt, h[i]); if (deep > lim) return false; if (maxcnt > (lim - deep) * a) return false;//加上这个剪枝就变得超快 if (deep == lim){ if (maxcnt >= 0)return false; else return true; } if (pos == n) return false; int cnt = max(h[pos - 1] / b, max(h[pos] / a, h[pos + 1] / b)) + 1; for (int cnt1 = 0; cnt1 <= cnt; cnt1++){ if (h[pos - 1] >= cnt1 * b) continue; h[pos - 1] -= b * cnt1; h[pos] -= a * cnt1; h[pos + 1] -= b * cnt1; for (int j = deep + 1; j <= deep + cnt1; j++) road[j] = pos; if (dfs(pos + 1, deep + cnt1, lim)) { for (int j = deep + 1; j <= deep + cnt1; j++) road[j] = pos; return true; } h[pos - 1] += b * cnt1; h[pos] += a * cnt1; h[pos + 1] += b * cnt1; } return false; } int main(){ memset(road, 0, sizeof(road)); cin >> n >> a >> b; int beg = 0; for (int i = 1; i <= n; i++) scanf("%d", h + i); for (int i = 1; i <= 15 * 15; i++) if (dfs(2, 0, i)){ printf("%d\n", i); for (int j = 1; j <= i; j++) printf("%d%c", road[j], j == i ? '\n' : ' '); break; } return 0; }
明天附上DP代码。晚安~
dp思路:
定义dp[i][j][k][z]表示目前攻击第i个人,j表示第i-1个人的生命,k表示第i个人的生命,z表示第i+1个人的生命。然后转移需要注意一下以下方面
①简单的转移:dp[i][j - b][k - a][z - b] = dp[i][j][k][z];
②如果j-b以后的生命值为0了,那么dp[i+1][k - a][z - b][h[i + 2]]=dp[i][j][k][z] + 1;
③如果j本来就为0,那么dp[i+1][k][z][h[i + 2]] = dp[i][j][k][z];
//看看会不会爆int!数组会不会少了一维! //取物问题一定要小心先手胜利的条件 #include <bits/stdc++.h> using namespace std; #define LL long long #define ALL(a) a.begin(), a.end() #define pb push_back #define mk make_pair #define fi first #define se second #define haha printf("haha\n") const int maxn = 30; const int inf = 0x3f3f3f3f; struct Point{ int i, j, k, z; Point(int i = 0, int j = 0, int k = 0, int z = 0): i(i), j(j), k(k), z(z){} }par[maxn][maxn][maxn][maxn]; int dp[maxn][maxn][maxn][maxn]; int n, a, b; int h[maxn]; int main(){ cin >> n >> a >> b; for (int i = 1; i <= n; i++){ scanf("%d", h + i); h[i]++; } memset(par, -1, sizeof(par)); memset(dp, 0x3f, sizeof(dp)); dp[2][h[1]][h[2]][h[3]] = 0; for (int i = 2; i <= n - 1; i++){ for (int j = h[i - 1]; j >= 0; j--){ for (int k = h[i]; k >= 0; k--){ for (int z = h[i + 1]; z >= 0; z--){ int nj = max(0, j - b), nk = max(0, k - a), nz = max(0, z - b); if (dp[i][nj][nk][nz] > dp[i][j][k][z] + 1){ dp[i][nj][nk][nz] = dp[i][j][k][z] + 1; par[i][nj][nk][nz] = Point(i, j, k, z); } if (nj == 0 && dp[i + 1][nk][nz][h[i + 2]] > dp[i][j][k][z] + 1){ dp[i + 1][nk][nz][h[i + 2]] = dp[i][j][k][z] + 1; par[i + 1][nk][nz][h[i + 2]] = Point(i, j, k, z); } if (j == 0 && dp[i + 1][k][z][h[i + 2]] > dp[i][j][k][z]){ dp[i + 1][k][z][h[i + 2]] = dp[i][j][k][z]; par[i + 1][k][z][h[i + 2]] = par[i][j][k][z]; } } } } } int ans = dp[n][0][0][0]; printf("%d\n", ans); Point tmp = Point(n, 0, 0, 0); while (ans--){ tmp = par[tmp.i][tmp.j][tmp.k][tmp.z]; printf("%d ", tmp.i); } return 0; }