ZOJ 3812 We Need Medicine 状压DP 回溯

题意:给出N种药,每种药有两个属性,重量W,伤害值T。给出Q种疾病,每个疾病也有两个属性:重量M,伤害值S。现在要求你对每种疾病设计选择药的方案,使:1.每种要至多使用一次。2.选出的药的重量的和等于该疾病的重量,选出的药的伤害值的和等于该疾病的伤害值。如果存在方案,输出任何一个方案。否则,输出No solution.

思路:很容易想到这是恰好装满的01背包问题。但是因为T的范围为200000,同时还要输出方案,会感到无从下手。。

          我们需要解决的问题有两个:因为T的范围非常大,如何保存dp的状态是个问题。同时也造成了无法直接保存最终的方案。

          我们首先求出dp方程。和多重部分和问题不同,这里的选择只有01,所以我们用一个bool类型来表示方案是否存在即可。则设dp[i]j][k]表示考虑前i种药,重量之和为j,伤害值为k的方案是否存在,取值为0或1.状态转移方程为:dp[i+1][j+w[i]][k+t[i]] |= dp[i][j][k]( |= 表示位运算或)。但是,如果直接用int保存该状态,会造成空间的极大浪费。所以我们用bitset类保存该状态,即在第三维用bitset保存。 则状态转移方程为:dp[i+1][j+w[i]][k<<t[i]] |= dp[i][j][k]。

但是,三维的dp也会爆内存,所以我们用滚动数组的方式来求最终的状态。

        而对于第二个问题,我们可以用搜索的方法解决。但是因为爆搜的复杂度是2^400,根本无法进行。在这道题中,我们虽然不能把三维的数组全部开满,但是我们可以将药进行分组,每一组记录一次dp状态,最后再搜索的时候,利用记录还原方案。(自己感觉有点可持久化数据结构的味道)。

代码如下:

<span style="font-size:12px;">#include <bits/stdc++.h>

using namespace std;

const int MAX = 43;
const int MAXN = 410;
const int MAXA = 51;
const int MAXB = 200001;

int N,Q,T;
int tot,m,s;
int w[MAXN],t[MAXN];
bool sig;
bitset<MAXB> archive[MAX][MAXA],dp[MAXA];
int ans[MAXN],sz;

bool dfs(int n, int m, int s)
{
    if(sig) return sig;
    if(m == 0 && s == 0){
        return sig = true;
    }
    int now = n / 10;
    if(n % 10) now++;
    if(archive[now][m][s] == 0)
        return false;
    if(m >= w[n] && s >= t[n]){
        ans[sz++] = n;
        if(!dfs(n-1,m - w[n],s - t[n]))
            sz--;
    }

    return dfs(n-1,m,s);
}


int main(void)
{
    //freopen("input.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    scanf("%d", &T);
    while(T--){
        scanf("%d %d", &N, &Q);
        for(int i = 1 ; i <= N; ++i)
            scanf("%d %d", &w[i],&t[i]);
        for(int i = 0 ; i <= 50; ++i)
            dp[i].reset();

        tot = 0;
        dp[0][0] = 1;
        for(int i = 1; i <= N; ++i){
            for(int j = 50; j >= w[i]; --j)
                dp[j] |= (dp[j - w[i]]<<t[i]);
            if(i == N || i % 10 == 0){
                tot++;
                for(int j = 0; j <= 50; ++j)
                    archive[tot][j] = dp[j];
            }
        }

        while(Q--){
            scanf("%d %d", &m,&s);
            if(dp[m][s]){
                sz = 0;
                sig = false;
                dfs(N,m,s);
                for(int i = 0; i < sz; ++i)
                    printf("%d%c",ans[i],i == sz -1?'\n':' ');
            }
            else
                puts("No solution!");
        }
    }
    return 0;
}
</span>


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值