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; }
BZOJ1021 [SHOI2008]循環的債務