四维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;
}
View Code

 

明天附上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;
}
View Code

 

posted @ 2016-10-22 23:40  知る奇迹に  阅读(173)  评论(0编辑  收藏  举报