ZOJ 3812 We Need Medicine(dp,状态压缩,2014牡丹江网络赛D题)

题目:We Need Medicine

题意:说白了还是个01背包,给出每种物品的两个属性w和t,对于给定的m和s,求是否存在方案使得取到的物品的w之和为m,t之和为s。如果存在方案还要输出任意一组解。

比赛的时候非常痛快地用尽人家服务器给的10S----TLE不知道几次。最后无果只能放弃。

对于方案的判断,我们可以简单的用01表示是否存在,但是题目数据量非常大,常规的做法的计算量是400 * 200000 * 50,10S肯定不够。比赛那会儿有想过位运算,最终还是没有写出来。

由于w只有50,可以用一个64位整型表示对应的值是否存在。dp[j]表示t的和为j时,可以对应到的m的和的集合。

那么对于新的物品w=x, t=y,dp[j+y] = dp[j+y] | (dp[j]<<w),状态转移还是好做的。关键是路径记录。

比赛的时候想到的是遍历当前状态集合,可是遍历还是相当于写个for循环,复杂度还是不对啊。

后来才想到,不要遍历当前集合,只是找到新状态与旧状态之间的差异,针对那几个新增的位做记录。这样的话,每个位置最多被更新一次。

然后对于每个位的判断,可以树状数组求最低位的做法,x&(-x),找到最低位的1,然后事先搞个map映射下就ok了,算完就把这个位去掉。

这里记录好了后面输出路径也就简单了。

#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long LL;
LL dp[200001];
int sel[200001][51];
int n, q, w[410], t[410], m[410], s[410];
map<LL, int> MP;
int main(){
    for(int i=0; i<=50; i++){
        MP[1LL<<i] = i;
    }
    int T;
    scanf("%d", &T);
    LL mask = (1LL<<51)-1;
    while(T--){
        scanf("%d %d", &n, &q);
        for(int i=1; i<=n; i++)  scanf("%d %d", w+i, t+i);
        int S = 0;
        for(int i=1; i<=q; i++){
            scanf("%d %d", m+i, s+i);
            if(s[i]>S)  S = s[i];
        }
        memset(dp, 0, sizeof(dp));
        memset(sel, 0, sizeof(sel));
        dp[0] = 1;
        for(int i=1; i<=n; i++){
            for(int j=S; j>=t[i]; j--){
                LL st = dp[j]|((dp[j-t[i]]<<w[i])&mask);
                LL c = dp[j]^st;
                dp[j] = st;
                while(c){
                    LL low = c&(-c);
                    int p = MP[low];
                    if(p>50)    break;
                    c ^= low;
                    sel[j][p] = i;
                }
            }
        }
        for(int i=1; i<=q; i++){
            if(sel[s[i]][m[i]]){
                int x=s[i], y=m[i];
                int z;
                while(x){
                    if(x!=s[i]) putchar(' ');
                    z = sel[x][y];
                    printf("%d", z);
                    x -= t[z];
                    y -= w[z];
                }
                puts("");
            }
            else{
                puts("No solution!");
            }
        }
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值