[BZOJ]1021
题意:
给出三个人之间的欠钱关系和他们各自持有的钱币种类和个数,求能不能各自把钱还清,如果不能输出”Impossible”,如果能输出最小的给钱张数。
题解:
Dp太神了!(弱菜自带遇Dp必跪flag)总之就是Dp。
可以按钱币种类划分阶段,那么方程可以为dp[i][j][k]表示用上了前i种钱币,达到了让第一个人有j块钱,第二个人有k块钱(因为总的钱数是一定的,确定了这两个第三个就确定了)所用的最小交换次数。那么最后的Ans就是dp[6][Tj][Tk]这样。Tj和Tk可以分别用Sa−b+c和Sb−c+a得到Sa是初始时给出的第一个人所拥有的钱数,Sb同理(即初始的减去给别人的加上别人给他的),最后就考虑如何转移了。
初始值dp[0][Sa][Sb]=0其它都是INF即最大值,转移就是枚举所有的情况像背包(?)一样去转移。这里需要明确一点,如果我们已知了第一个人为了达到j钱给钱或者拿钱的张数和第二个人为了达到k钱给钱或者拿钱的张数,那么这个转移中需要统计的总共交换钱币张数就是(abs(DeltaA)+abs(DeltaB)+abs(DeltaA+DeltaB))/2,这个式子具体就是A改变的加上B改变的加上C改变的最后除去重复的。然后枚举一下j和
代码
#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;
}

本文介绍了一道BZOJ题目1021的解题思路,利用动态规划方法解决三人间的债务问题,并通过最优钱币交换次数确保债务清偿。
199

被折叠的 条评论
为什么被折叠?



