BZOJ 1021 [Debt 循环的债务]

题面

题意

  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;
}

猜你喜欢

转载自www.cnblogs.com/Kilo-5723/p/12717234.html