[BZOJ]1021 循环的债务

[BZOJ]1021

题意:

  给出三个人之间的欠钱关系和他们各自持有的钱币种类和个数,求能不能各自把钱还清,如果不能输出”Impossible”,如果能输出最小的给钱张数。

题解:

  Dp太神了!(弱菜自带遇Dp必跪flag)总之就是Dp。  
  可以按钱币种类划分阶段,那么方程可以为 dp[i][j][k] 表示用上了前i种钱币,达到了让第一个人有j块钱,第二个人有k块钱(因为总的钱数是一定的,确定了这两个第三个就确定了)所用的最小交换次数。那么最后的 Ans 就是 dp[6][Tj][Tk] 这样。 Tj Tk 可以分别用 Sab+c Sbc+a 得到 Sa 是初始时给出的第一个人所拥有的钱数, Sb 同理(即初始的减去给别人的加上别人给他的),最后就考虑如何转移了。
  初始值 dp[0][Sa][Sb]=0 其它都是 INF 即最大值,转移就是枚举所有的情况像背包(?)一样去转移。这里需要明确一点,如果我们已知了第一个人为了达到j钱给钱或者拿钱的张数和第二个人为了达到k钱给钱或者拿钱的张数,那么这个转移中需要统计的总共交换钱币张数就是 (abs(DeltaA)+abs(DeltaB)+abs(DeltaA+DeltaB))/2 ,这个式子具体就是A改变的加上B改变的加上C改变的最后除去重复的。然后枚举一下 j k,接着枚举一下当前钱币数目,就可以进行转移,其实这部分转移看代码比较容易理解,口述真的感人。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define INF 33686018

using namespace std;

const int Maxn = 1010;

int val[7] = {0,100,50,20,10,5,1},cnt[Maxn][Maxn],d[Maxn],e[Maxn],sum,a,b,c,dp[3][Maxn][Maxn];

int main(){
    scanf("%d%d%d",&a,&b,&c);
    for(int i = 1;i <= 3;i++){
        for(int j = 1;j <= 6;j++){
            scanf("%d",&cnt[i][j]);
            sum += cnt[i][j] * val[j];//统计总钱数 
            d[i] += cnt[i][j] * val[j];//统计第i个人的初始总钱数 
            e[j] += cnt[i][j];//统计第j种钱币的总钱数 
        }
    }
    int Ta = d[1] - a + c,Tb = d[2] - b + a;
    if(Ta < 0 || Tb < 0 || sum - Ta - Tb < 0){printf("impossible\n");return 0;}
    memset(dp[0],2,sizeof(dp[0]));int pre = 0,now = 0;dp[0][d[1]][d[2]] = 0;
    for(int i = 1;i <= 6;i++){
        pre = now;now ^= 1;memset(dp[now],2,sizeof(dp[now]));
        for(int j = 0;j <= sum;j++){
            for(int k = 0;j + k <= sum;k++){
                if(dp[pre][j][k] == INF)continue;
                dp[now][j][k] = min(dp[now][j][k],dp[pre][j][k]);//相当于初值 
                for(int l = 0;l <= e[i];l++){
                    for(int o = 0;o + l <= e[i];o++){
                        int DeltaA = l - cnt[1][i],DeltaB = o - cnt[2][i];
                        int TCost1 = j + DeltaA * val[i],TCost2 = k + DeltaB * val[i];
                        if(TCost1 < 0 || TCost2 < 0 || sum - TCost1 - TCost2 < 0)continue;
                        dp[now][TCost1][TCost2] = min(dp[now][TCost1][TCost2],dp[pre][j][k] + (abs(DeltaA) + abs(DeltaB) + abs(DeltaA + DeltaB)) / 2);

                    }
                }

            }
        }
    }
    if(dp[now][Ta][Tb] == INF){printf("impossible\n");return 0;}
    else printf("%d\n",dp[now][Ta][Tb]);
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值