BZOJ1021 [SHOI2008]循环的债务

Description

  Alice、Bob和Cynthia总是为他们之间混乱的债务而烦恼,终于有一天,他们决定坐下来一起解决这个问题。

不过,鉴别钞票的真伪是一件很麻烦的事情,于是他们决定要在清还债务的时候尽可能少的交换现金。比如说,Al

ice欠Bob 10元,而Cynthia和他俩互不相欠。现在假设Alice只有一张50元,Bob有3张10元和10张1元,Cynthia有3

张20元。一种比较直接的做法是:Alice将50元交给Bob,而Bob将他身上的钱找给Alice,这样一共就会有14张钞票

被交换。但这不是最好的做法,最好的做法是:Alice把50块给Cynthia,Cynthia再把两张20给Alice,另一张20给

Bob,而Bob把一张10块给C,此时只有5张钞票被交换过。没过多久他们就发现这是一个很棘手的问题,于是他们找

到了精通数学的你为他们解决这个难题。

Input

  输入的第一行包括三个整数:x1、x2、x3(-1,000≤x1,x2,x3≤1,000),其中 x1代表Alice欠Bob的钱(如

果x1是负数,说明Bob欠了Alice的钱) x2代表Bob欠Cynthia的钱(如果x2是负数,说明Cynthia欠了Bob的钱) x3

代表Cynthia欠Alice的钱(如果x3是负数,说明Alice欠了Cynthia的钱)

接下来有三行

每行包括6个自然数:

a100,a50,a20,a10,a5,a1

b100,b50,b20,b10,b5,b1

c100,c50,c20,c10,c5,c1

a100表示Alice拥有的100元钞票张数,b50表示Bob拥有的50元钞票张数,以此类推。

另外,我们保证有a10+a5+a1≤30,b10+b5+b1≤30,c10+c5+c1≤30,而且三人总共拥有的钞票面值总额不会

超过1,000。

Output

  如果债务可以还清,则输出需要交换钞票的最少张数;如果不能还清,则输出“impossible”(注意单词全部

小写,输出到文件时不要加引号)。

Sample Input

输入一

10 0 0

0 1 0 0 0 0

0 0 0 3 0 10

0 0 3 0 0 0

输入二

-10 -10 -10

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

Sample Output

输出一

5

输出二

0

HINT

对于100%的数据,x1、x2、x3 ≤ |1,000|。

题解

可以发现他们把钱还清等价于三个人最后持有的钱数为他们应该有的钱(即他们本来有的减他们欠别人的加别人欠他们的)。

设他们的目标钱数分别是$Ta,Tb,Tc$,

令$f_{k,i,j}$表示只使用第$k$种至第$5$种(种类为$0..5$)钱币,且现在A持有$i$元,B持有$j$元(C持有$S-i-j$元,其中$S$是总钱数),在此条件下要使他们达到目标状态最少需要交换多少钱币。

边界条件:

$$f_{6,i,j}=\begin{cases}
0 & i = Ta, j = Tb\\
\infty & otherwise
\end{cases}$$

转移时,由于A给B钱B再给C钱没有必要(如果B给C的比A给B的多,转化为A给C,B给C;否则,转化为A给B,A给C),所以只有6种情况,即某两个人都给另一个人,或某一个人给另外两个人(其实还有某个人不参与交换或全都不交换,只需看做给了0个,也可以当成上述六种情况之一)。

从大到小转移时由于好多状态用不到(比如A本来有103元时$f_{1,102,0}$用不到,因为只有整百地取),记忆化搜索即可。

代码中加了一个剪枝:若某人把剩下的钱还完也还不清,直接返回INF。

加了这个剪枝后运行时间少了近一半。(bzoj上1312s->764s)

附代码:

#include <algorithm>
#include <cstdio>
using std::min;
const int N = 1050;
const int INF = 100000000;
int f[6][N][N];
int a[6], b[6], c[6];
int sa[6], sb[6], sc[6], sum;
const int v[6] = {100, 50, 20, 10, 5, 1};
int Ta, Tb, Tc;
int dfs(int k, int aa, int bb) {
  if (k == 6) return aa == Ta && bb == Tb ? 0 : INF;
  int cc = sum - aa - bb;
  if (aa - Ta > sa[k] || bb - Tb > sb[k] || cc - Tc > sc[k])
    return INF;
  if (~f[k][aa][bb]) return f[k][aa][bb];
  int &ans = f[k][aa][bb];
  ans = INF;
  for (int lac = 0; lac <= a[k]; ++lac)
    for (int lbc = 0; lbc <= b[k]; ++lbc)
      ans = min(ans, dfs(k + 1, aa - lac * v[k], bb - lbc * v[k]) + lac + lbc);
  for (int lab = 0; lab <= a[k]; ++lab)
    for (int lcb = 0; lcb <= c[k]; ++lcb)
      ans = min(ans, dfs(k + 1, aa - lab * v[k], bb + (lab + lcb) * v[k]) + lab + lcb);
  for (int lba = 0; lba <= b[k]; ++lba)
    for (int lca = 0; lca <= c[k]; ++lca)
      ans = min(ans, dfs(k + 1, aa + (lba + lca) * v[k], bb - lba * v[k]) + lba + lca);
  for (int lac = 0; lac <= c[k]; ++lac)
    for (int lbc = 0; lbc + lac <= c[k]; ++lbc)
      ans = min(ans, dfs(k + 1, aa + lac * v[k], bb + lbc * v[k]) + lac + lbc);
  for (int lab = 0; lab <= b[k]; ++lab)
    for (int lcb = 0; lcb + lab <= b[k]; ++lcb)
      ans = min(ans, dfs(k + 1, aa + lab * v[k], bb - (lab + lcb) * v[k]) + lab + lcb);
  for (int lba = 0; lba <= a[k]; ++lba)
    for (int lca = 0; lca + lba <= a[k]; ++lca)
      ans = min(ans, dfs(k + 1, aa - (lba + lca) * v[k], bb + lba * v[k]) + lba + lca);
  return ans;
}
int main() {
  int gab, gbc, gca;
  scanf("%d%d%d", &gab, &gbc, &gca);
  for (int i = 0; i < 6; ++i) scanf("%d", &a[i]);
  for (int i = 0; i < 6; ++i) scanf("%d", &b[i]);
  for (int i = 0; i < 6; ++i) scanf("%d", &c[i]);
  sa[5] = a[5];
  for (int i = 4; ~i; --i) sa[i] = sa[i + 1] + a[i] * v[i];
  sb[5] = b[5];
  for (int i = 4; ~i; --i) sb[i] = sb[i + 1] + b[i] * v[i];
  sc[5] = c[5];
  for (int i = 4; ~i; --i) sc[i] = sc[i + 1] + c[i] * v[i];
  Ta = sa[0] - gab + gca;
  Tb = sb[0] - gbc + gab;
  Tc = sc[0] - gca + gbc;
  if (Ta < 0 || Tb < 0 || Tc < 0) return puts("impossible"), 0;
  std::fill(f[0][0], f[6][0], -1);
  int ans = dfs(0, sa[0], sb[0]);
  if (ans > 1000) return puts("impossible"), 0;
  printf("%d\n", ans);
  return 0;
}

  

时间: 2024-10-06 01:05:30

BZOJ1021 [SHOI2008]循环的债务的相关文章

BZOJ1021 SHOI2008循环的债务

dp模拟即可. d[i][j][k]表示使用前i种面值,1号手里钱为j,2号手里钱为k时最少操作数 使用滚动数组压缩空间 #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int n = 6; const int M = 1005; const int val[n] = {1, 5, 10, 20, 50, 100}; int x1, x2, x3;

[SHOI2008]循环的债务

https://www.zybuluo.com/ysner/note/1312307 题面 戳我 解析 感觉比较套路. 当然前提是注意到各币值互不影响. 那就简单了. 设\(f[i][j][k]\)表示到第\(i\)种币值,\(A\)有\(i\)元,\(B\)有\(j\)元.显然钱的总量固定,\(C\)的钱数可以顺带算出来. 这样起始状态和最终状态都知道. 然后枚举下他们交易完后各剩多少张该币值的钱,直接转移即可. 若设\(x,y,z\)分别表示\(A,B,C\)钱数的变化量,那么实际交换次数为

bzoj千题计划111:bzoj1021: [SHOI2008]Debt 循环的债务

http://www.lydsy.com/JudgeOnline/problem.php?id=1021 如果A收到了B的1张10元,那么A绝对不会把这张10元再给C 因为这样不如B直接给C优 由此可以推出 若A欠B20元,B欠C 30元, 那么A还C20元,B还C10元最优 所以一共只有 A->BC   B->AC  C->AB AB->C  BC->A  AC->B 这6种转移情况 根据输入,我们可以知道三人最终手中有多少钱ea.eb.ec,一共有多少钱sum 设f

BZOJ 1021: [SHOI2008]Debt 循环的债务( dp )

dp(i, j, k)表示考虑了前i种钱币(从小到大), Alice的钱数为j, Bob的钱数为k, 最小次数. 脑补一下可以发现, 只有A->B.C, B->A.C, C->A.B, A.B->C, A.C->B, B.C->A 6情况, 枚举然后dp一下就OK了. dp用刷表的话,有个强有力的剪枝是之后的硬币无论如何组合都无法满足时不去更新. --------------------------------------------------------------

【BZOJ】【1021】【SHOI2008】Dept循环的债务

DP 去膜拜题解了>_>玛雅原来是动规…… 让我先理解一下为什么要用动规:这个题根据钱数推方案其实是无从下手的……(线性规划?……事实证明我想多了) 啦-我们先来看个超级简化版的问题:怎么判无法还清?正着判很麻烦对不对= =(其实是我没想……) 那么我们倒着来考虑:有哪些状态是我们通过交换钱币能够到达的,这个可以递推对不>_> 现在我们就知道哪些状态我们是可以到达的了……再多想一下……递推……如果我们依次考虑每种面额的交换策略,顺便也就知道了我们到达这个状态的最小交换次数对吧? 原

BZOJ_1021_[SHOI2008]_Debt循环的债务_(DP)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1021 三个人相互欠钱,给出他们每个人各种面额的钞票各有多少张,求最少需要传递多少张钞票才能把账还清. 分析 用\(f[i][j][k]\)表示用过前\(i\)种钞票后,A有\(j\)元,B有\(k\)元所需要的步数. 然后DP就好了. 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn=1000+5,IN

【BZOJ 1021】 [SHOI2008]Debt 循环的债务

1021: [SHOI2008]Debt 循环的债务 Time Limit: 1 Sec  Memory Limit: 162 MB Submit: 605  Solved: 302 [Submit][Status] Description Alice.Bob和Cynthia总是为他们之间混乱的债务而烦恼,终于有一天,他们决定坐下来一起解决这个问题.不过,鉴别钞票的真伪是一件很麻烦的事情,于是他们决定要在清还债务的时候尽可能少的交换现金.比如说,Alice欠Bob 10元,而Cynthia和他俩

BZOJ 1021 [SHOI2008]Debt 循环的债务

1021: [SHOI2008]Debt 循环的债务 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 694  Solved: 356[Submit][Status][Discuss] Description Alice.Bob和Cynthia总是为他们之间混乱的债务而烦恼,终于有一天,他们决定坐下来一起解决这个问题.不过,鉴别钞票的真伪是一件很麻烦的事情,于是他们决定要在清还债务的时候尽可能少的交换现金.比如说,Alice欠Bob 10元,而Cyn

BZOJ1021 [SHOI2008]Debt 循环的债务

貌似去年暑假就听过这道题...那时候还YY了个什么平面三条轴,夹角Π/3之类的... 正解嘛...当然是DP 令f[i][j][k]表示到了第i种面值,第一个人还有j元钱,第二个人还有k元钱的最少交换张数. 于是就是个背包问题的说,然后因为dp方程太复杂了,请参考程序吧... (还有个非常厉害的剪枝我的程序没加,因为太懒了≥v≤......誒!不要打我啊~~~都过了嘛好不好QAQ) 1 /******************************************************