Hỏi thuật toán bài toán tìm tổng số cách đổi tiền có thể được

.


Mình có đề về bài toán ATM và dùng for để tìm ra số cách rút nhưng bị giới hạn thời gian hết 50%. Mạo muội đăng lên hỏi mọi người có cách nào để nó chạy nhanh hơn ko ạ :((
P.s: m20 m50 m100 m200 m500 tương ứng với số tiền 20k, 50k, 100k, 200k, 500k ạ. Em gán sẵn vậy cho nó ngắn ^^

int SoCach(int n) 
    {
    	int dem = 0;
    	for (int a = 0; a <= n / m20; a++)
    		for (int b = 0; b <= n / m50; b++)
    			for (int c = 0; c <= n / m100; c++)
    				for (int d = 0; d <= n / m200; d++)
    					for (int f = 0; f <= n / m500; f++)
    						if (a*m20 + b*m50 + c*m100 + d*m200 + f*m500 == n)
    							dem++;
    	return dem;
    }

Phát triển thuật toán lên nhé:
https://daynhauhoc.com/t/help-thuat-toan-doi-tien-3-loai-giay-bac-tim-phuong-an-tat-ca/

2 Likes

Mình nghĩ là có thể quy về tìm nghiệm số tự nhiên của 50x + 20y + 10z + 5w + 2v = n
Cái này có thể cắt ra làm 2 phương trình:
50x + 20y + 10z = 10w và 10w + 5w + 2v = n
Rồi tìm cách giải tiếp như link ở trên, hoặc ở link này https://www.reddit.com/r/learnmath/comments/2zkmr8/number_theory_linear_diophantine_equations_on_3/ để tìm ra công thức nghiệm nguyên tổng quát, rồi bắt đầu khoanh vùng solution từ công thức đó với nghiệm số tự nhiên.
Mình thấy rằng nếu phương trình gốc có n variable thì công thức tổng quát sẽ phụ thuộc tiếp vào n - 1 variable, tính mệt thấy mồ, không biết có đường tắt không.

2 Likes

Bài này không có liệt kê nghiệm :smiley:

http://diendan.congdongcviet.com/threads/t386516::bai-toan-quy-hoach-dong.cpp?p=890568#post890568

format vỡ lung tung, nên click vào link

viết giải thích dài dòng lắm

gọi số cách thối tiền là w(n, V) với:

  • n là số tiền cần thối, ví dụ 15,
  • V là mảng chứa mệnh giá tiền, ví dụ [10,5,2]
    thì với đệ quy phải tìm cách đi tới w(n, V) với bước đi nhỏ nhất. Ở đây số cách thối 15 đồng có thể hiểu là bằng số cách thối 13 đồng (thêm 2 đồng nữa là thành 15). Nhưng chưa hết, vì với cách tính này thì 15 đồng tiền thối lúc nào cũng có tờ 2 đồng, vì vậy còn phải đếm các lần thối ko cần tờ 2 đồng nữa, hay w(15, [10,5,2]) = w(13, [10,5,2]) + w(15, [10,5]). Ở đây chỉ cần xét 1 loại mệnh giá thôi vì các mệnh giá kia sẽ được xử lý trong w(15, [10,5]) hay w(13, [10,5,2]).

để viết công thức tổng quát thì cần thêm 1 biến m nữa, là số lượng tờ tiền trong V. w(n, V) chuyển thành W(n, V, m). Viết W(15, [10,5,2], 3) hiểu là w(15, [10,5,2]); W(15, [10,5,2], 2) hiểu là w(15, [10,5]). Quy ước thêm nữa V[1] là mệnh giá đầu tiên trong V, V[m] là mệnh giá cuối cùng.

vậy ta có công thức đệ quy tổng quát: W(n, V, m) = W(n - V[m], V, m) + W(n, V, m-1)

đệ quy thì phải có điều kiện dừng, hay base cases. Điều kiện dừng ở đây là

  • nếu n = 0, thì W(0, V, m) = 1
  • nếu n < 0, thì W(n, V, m) = 0
  • nếu n > 0 và m < 1, thì W(n, V, m) = 0

code C++14:

#include <iostream>
#include <vector>

using IntArray = std::vector<int>;

int W(int n, const IntArray& V, int m)
{
    if (n == 0) return 1;
    if (n < 0 || m < 1) return 0;
    return W(n - V[m-1], V, m) + W(n, V, m-1);
}

int main()
{
    IntArray V{50,20,10,5,2};
    std::cout << W(325, V, V.size()) << "\n";
    std::cout << W(876, V, V.size()) << "\n";
    std::cout << W(3, V, V.size()) << "\n";
}

cách này hơi chậm vì là đệ quy. Muốn nhanh hơn thì xài quy hoạch động, nói đơn giản là lưu lại các giá trị đã tính rồi để ko phải tính lại như đệ quy nữa.

với quy hoạch động thì đi ngược lại từ nhỏ lên. Ở đây n tối đa là 1000 (10 triệu, mà là bội của 10 ngàn, nên chỉ có 1000 giá trị từ 1 tướng ứng với 10 ngàn tới 1000 tương ứng với 10 triệu), nên làm hẳn cái mảng 1000+1 phần tử luôn (thêm số 0), rồi đi từ nhỏ tới lớn. Kiểu na ná như Fibonacci ấy. Fibonacci đệ quy là F(n) = F(n-1) + F(n-2), thì thay vì đi từ F(1000) xuống F(999), F(998), F(997), v.v… thì ta có thể đi từ F(0) F(1) F(2) lên F(1000).

ở đây khó hơn vì W(n, V, m) có tới 2 giá trị thay đổi là n và m. Cách giải là giữ nguyên 1 giá trị, ở đây là m, rồi tính W với n từ 0 tới 1000. Sau đó tăng m lên 1 đơn vị, rồi tính lại W từ 0 tới 1000, v.v… tới khi m = size(V).

với m ko đổi, ta có W(n, V, m) = [COLOR="#008000"]W(n, V, m) +[/COLOR] W(n - V[m], V, m). Sở dĩ phải cộng dồn là vì m thay đổi, nên phần xanh lá thực chất chính là từ m cũ, hay là W(n, V, m-1).
ví dụ n = 15, V = [10,5,2]. Ta tạo mảng p[0…15] gồm 16 phần tử, p[0] = 1 vì có đúng 1 cách thối 0 đồng:
[FONT=Courier New] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
m=0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[/FONT]

với m = 1, bắt đầu từ p[10] tới p[15], gán p[i] += p[i-10]
[FONT=Courier New] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
m=1 1 0 0 0 0 0 0 0 0 0 [COLOR="#FF0000"]1 0 0 0 0 0[/COLOR][/FONT]

với m = 2, bắt đầu từ p[5] tới p[15], gán p[i] += p[i-5]
[FONT=Courier New] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
m=2 1 0 0 0 0 [COLOR="#FF0000"]1 0 0 0 0 2 0 0 0 0 2[/COLOR][/FONT]

với m = 3, bắt đầu từ p[2] tới p[15], gán p[i] += p[i-2]
[FONT=Courier New] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
m=3 1 0 [COLOR="#FF0000"]1 0 1 1 1 1 1 1 3 1 3 1 3 3[/COLOR][/FONT]

2 Likes

Các thánh trong đó dùng kí hiệu cao siêu quá, mình đọc chẳng ghi nhận được gì @@

1 Like

Bài này dùng QHĐ mới ra :smiley: cho test mảng dữ liệu 10^6 mảng mệnh giá 1000 mới fer :slight_smile:

1 Like

hì. Vấn đề là chỗ QHĐ đó anh ei. Mới năm nhất mà… Coi coi lơ tơ mơ chứ ko hiểu lắm… Nên ko dám chép code bừa bãi để nộp ạ.
Cùng 1 người hỏi bài này trên congdongcviet mà a :smiley: E chĩ mong là có hướng lẹ hơn 5 dòng for này thôi. Chứ QHĐ hay Đệ Quy là thua. Ko dám chép vào vì chưa hiểu ngọn ngành

Chưa cần tới mấy khái niệm cao siêu các bác trên nói, bạn trả lời hộ mình 2 câu hỏi.

  1. Tại sao lại cho for chạy từ mênh giá nhỏ nhất rồi lồng vòng for các mệnh giá to dần mà không làm ngược lại.
  2. Tại sao lại phải chạy vòng for tới max số tờ tiền có thể đổi được của mỗi mệnh giá mà không lấy số tiền cần đổi trừ đi số tiền đã được đổi ở mỗi mệnh giá rồi mới chạy vòng for tới max số tờ tiền có thể đổi sau khi đã trừ đi
3 Likes

À…

  1. Thì tại e nghĩ đi từ bé đến lớn với từ lớn tới bé thì cách chạy nó vẫn như nhau mà anh??(Theo em nghĩ là vậy. Có sai thì mong anh bỏ qua)
  2. Vậy có lẽ nếu như 1 dòng for rồi đếm cách đổi ở mỗi mệnh giá thì khả thi và ngắn hơn nếu cho nó chạy tới max tờ tiền nhỉ?

Khác nhau chứ, làm ngược lại thì sẽ lợi dụng được kết quả của phép tính trước để ngắt sớm vòng for, hơn nữa nếu tinh ý thì sẽ thấy ngắt bỏ được hẳn 1 vòng lặp

2 Likes

À. Tức h là em chuyển lại chạy từ 500k trở về phải ko a??

Yep, mà nhớ ở mõi vòng for phải trừ dần số tiền đã đổi được, thêm code check ngắt sớm vòng for vì không phải lúc nào cũng có thể trả tiền được, ở vòng for trong cùng có thể bỏ vì chỉ còn đúng 1 mệnh giá cần đổi, chia lấy mod trực tiếp luôn cũng được, lúc này bạn sẽ thấy kết quả của phép tính trước được dùng lại, suy rộng ra cũng là kiểu quy hoạch động đấy

3 Likes

Yes. Em mới test chạy từ 500k rồi biến sau là 200k chạy đến n-a*500k (a là biến chạy của 500k)

Viết chưa sạch nước cản mà đụng vào bài dạng này thì khó cả cho bạn và người trả lời thật. Chủ đề này bên HSG sau khi bạn học quay lui chán chê rồi mới dạy đấy.

2 Likes

Hic.Em cũng đâu muốn đụng đâu anh ơi! BT trên trường cho thì phải làm để hiểu + có điểm thôi ^^

Cách này là nhanh nhất rồi vì giới hạn số tiền khá nhỏ nên không xơi mem nhiều, phù hợp với QHĐ (tức là chỉ cần vài nghìn bước truy cập mem). Để ý kết quả lên đến hàng triệu, nghĩa là phải hàng chục triệu phép tính được thực hiện để thử sai.

3 Likes

Mình đang hỏi chủ thớt để khai sáng thôi chứ bạn không cần phải thể hiện gì với mình đâu, mục tiêu vẫn là chủ thớt có thể tự làm được bài 1 cách tối ưu nhất với kiến thức mà chủ thớt đang có

2 Likes
  1. Sở dĩ sai là vì t2,t3, t4 tính chưa đúng, và chưa break vòng lặp khi điều kiện if đúng
  2. Code vẫn chưa tận dụng được kết quả phép tính trước, hay còn gọi là chưa tối ưu, nên dự là vẫn chậm
2 Likes

Hic. Tính t2 t3 t4 sai à anh? :frowning:

Chính xác phải là n - số tiền đã được đổi
VD: t2 = n - a500 - b 200

2 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?