题面
题意
A, B, C 互相欠对方一些钱,每个人持有一些面额为 \(100,50,20,10,5,1\) 的纸币,纸币总额不超过 \(1000\),求能还清债务的最少纸币交换次数。
题解
DP。用 \(dp_{i,j}\) 代表 A 持有 \(i\) 金额,B 持有 \(j\) 金额的最少纸币交换次数。显然一张纸币至多被交出一次,任何转手的情况都可以用一步到位的操作代替,因此不用担心交出纸币行为的后效性,可以 DP。
有两种思路,一种是普通地对每张纸币做一次 DP,一种是按面额从大到小依次做所有同一面额的纸币分配。后者虽然看起来复杂度较高,但优点是在处理每个面额时,不为 \(+\infty\) 的 \(dp_{i,j}\) 对 A, B 初始的财富关于处理过的面额的最小公倍数同余。因为这道题目限制条件多且杂,讨论具体复杂度意义不大,从实践结果来看第一种方法卡常也可以过,但第二种方法的时间稍微宽裕一些。这里贴出第二种方法的实现代码。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e3+5,inf=1e9;
const int val[6]={100,50,20,10,5,1},mod[6]={10000,100,50,10,10,5};
int dp[maxn][maxn],tmp[maxn][maxn];
int a[3][6],b[3],p,q;
int _abs(int n){ return n<0?-n:n; }
inline void update(int &a,int b){ a=a<b?a:b; }
void give(int a,int b,int c,int v,int m){
int i,j,k;
int x,y,z,t=a+b+c;
for (x=p%m;x<maxn;x+=m) for (y=q%m;y<maxn;y+=m) tmp[x][y]=dp[x][y];
for (x=p%m;x<maxn;x+=m) for (y=q%m;y<maxn;y+=m) if (tmp[x][y]<inf){
for (i=0;i<=t;i++) for (j=t-i;j>=0;j--)
update(dp[x-(a-i)*v][y-(b-j)*v],tmp[x][y]+_abs(a-i)+_abs(b-j)+_abs(a+b-i-j));
}
}
int main(){
int i,j;
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
for (i=0;i<3;i++) for (j=0;j<6;j++){
scanf("%d",&a[i][j]); b[i]+=a[i][j]*val[j];
}
for (i=0;i<maxn;i++) for (j=0;j<maxn;j++)
dp[i][j]=inf;
dp[b[0]][b[1]]=0;
p=b[0]; q=b[1];
b[0]-=x-z; b[1]-=y-x; b[2]-=z-y;
for (i=0;i<6;i++) give(a[0][i],a[1][i],a[2][i],val[i],mod[i]);
if (b[0]<0||b[0]>=maxn||b[1]<0||b[1]>=maxn){
puts("impossible"); return 0;
}
if (dp[b[0]][b[1]]==inf) puts("impossible");
else printf("%d\n",dp[b[0]][b[1]]/2);
return 0;
}