Hỏi ý tưởng tìm tổ hợp k số trong 1 dãy số cho trước thoả mãn điều kiện

Chào mọi người.
Hiện em đang có 1 một bài toán nhưng chưa tìm được cách nào để tối ưu. Em xin phép đăng lên đây để xin ý kiến của mọi người ạ.

Cho một danh sách các số có n (10 <= n <= 60) số x1, x2,..., xn sao cho 1 <= xj <= 10. Nhập số k (1 <= k <= 8), nhập số m (0 <= m <= 9). Tìm 1 tổ hợp k số trong danh sách số đã cho sao cho tổng của k số tìm được modulo 10 bằng m.
Vd: cho dãy số N = {1,3,5,6,3,7,2,9,10,3,4,6} với k = 3 và m = 5 thì một trong các tổ hợp thõa mãn là {5,6,4} -> (5 + 6 + 4) % 10 = 5.

Em hiện tại chưa nghĩ ra được cách nào khác ngoài dùng đê quy để vét cạn. nhưng với cách này thì em thấy không tối ưu lắm. Xin mọi người góp ý giúp ạ :grinning:

chỉ cần tìm ra 1 tổ hợp là xong à :V

tuy n lên tới 60 nhưng 1 <= xj <= 10 nên chỉ có 10 giá trị trong 60 số này, vét cạn chắc cũng lẹ mà :V :V

2 Likes

Hỏi là quyền của bạn, đăng bài là quyền của bạn, bạn không cần phải xin phép đâu :smile:

Chọn k số trong n số sao cho tổng của chúng chia 10 mod n, vậy có tối đa 60C8 = 2,558,620,845 cách :stuck_out_tongue:

Vét cạn chắc ăn được 80% số test :stuck_out_tongue_winking_eye:

1 Like

dùng vét cạn không ăn hết case ạ. bị outime bác ơi nên em mới lên đây xin ý kiến ạ :sweat_smile:

bác vui tính thật :smiley: chỉ là em muốn tìm hướng tối ưu để full test ạ

Bài này chắc QHĐ được.

Gọi subset_count là 1 mảng gồm 10 phần tử, subset_count[i] là số lần số i (0 <= i < 10) xuất hiện trong 1 tập con. Ví dụ, nếu subset = {1, 1, 2, 3, 4} thì subset_count = [0, 2, 1, 1, 1, 0, 0, 0, 0, 0].

Gọi f[len][subset_count] là số dư của tập subset (có tập subset_count tương ứng với subset), len là số lượng số đã chọn. Tất nhiên

\sum_{i=0}^{9} subset\_count[i] = len\\ \left(\sum_{i=0}^{9} i * subset\_count[i]\right) \% 10 = f[len][subset\_count]

Nếu ta kết nạp thêm 1 số x vào tập con hiện tại, ta có được công thức mới như sau:

subset\_count' = subset\_count\\ subset\_count'[x]++\\ f[len+1][subset\_count'] = (f[len][subset\_count] + x) \% 10

Ta tìm kiếm xem có bất kì dãy subset_count nào thoả mãn f[k][subset_count] == m hay không, sau đó in ra dãy con là được.


Thuật toán đã ổn. Vấn đề ở đây còn lại là lưu cấu trúc dữ liệu gì cho subset_count để đỡ tốn bộ nhớ. Ở đây bạn để ý 10 \le n \le 60, giới hạn rất bé nên bạn thử tìm cách mã hoá subset_count sang 1 cấu trúc dữ liệu khác mảng xem.

4 Likes

này là vét cạn “thông minh” :crazy_face: xài 9 vòng for :crazy_face:

gọi count(x) là hàm đếm số số x trong mảng N. Ví dụ dãy số N = {1,3,5,6,3,7,2,9,10,3,4,6} thì count(6) = 2

nhận thấy số 10 cộng vào mod 10 thì giá trị trả về ko thay đổi nên ko cần làm vòng for cho số 10. Bài toán trở thành chọn k’ số sao cho k-count(10) <= k’ <= k

số số 1/2/3/4/5/6/7/8/9 chỉ có thể là từ 0 tới k’, cho chạy tới k’-1 thôi vì trường hợp k’ tối đa thì mấy số kia chỉ còn có thể là 0 hết, vậy cần ktra 9 trường hợp k’ tối đa riêng. k’ tối đa là 8, vậy xét số số 1/2/3/4/5/6/7/8/9 từ 0 tới 7 là được.

// cần ktra 9 trường hợp c1 = k, c2 = k, c3 = k, ..., c9 = k

for (int c1 = 0; c1 < k; ++c1) {
  for (int c2 = 0; c2 < k; ++c2) {
    for (int c3 = 0; c3 < k; ++c3) {
      for (int c4 = 0; c4 < k; ++c4) {
        for (int c5 = 0; c5 < k; ++c5) {
          for (int c6 = 0; c6 < k; ++c6) {
            for (int c7 = 0; c7 < k; ++c7) {
              for (int c8 = 0; c8 < k; ++c8) {
                for (int c9 = 0; c9 < k; ++c9) {
                }
              }
            }
          }
        }
      }
    }
  }
}

9 vòng for này chạy từ 0 tới 7, chỉ mất tối đa 8^9 ~ 134tr lần thôi :crazy_face:

const int maxCount = k;
const int minCount = k - count(10);
int totalCount = 0;
for (int c1 = 0; c1 < k; ++c1) {
  if (c1 > count(1)) continue;
  totalCount = c1;
  if (minCount > totalCount || totalCount > maxCount) continue;
  for (int c2 = 0; c2 < k; ++c2) {
    if (c2 > count(2)) continue;
    totalCount = c1 + c2;
    if (minCount > totalCount || totalCount > maxCount) continue;
    for (int c3 = 0; c3 < k; ++c3) {
      ...
    }

duyệt 9 vòng for khủng quá thì nghĩ cách khác duyệt 1 vòng for :relieved:
nhận thấy số số x chỉ có giá trị từ 0 tới 7, vừa vặn trong 3 bit. 9 số vừa vặn trong 27 bit. Vậy cho i chạy từ 0 tới 2^27 - 1 là duyệt hết 8^9 số này. Lúc này c1 = 3 bit thấp nhất của i, c2 là 3 bit thấp tiếp theo của i, v.v…

// check 10 trường hợp k tối đa
for (int i = 1; i <= 10; ++i) {
  if (k > count(i)) continue;
  const int sum = i * k;
  if (sum % 10 == m) {
    std::cout << "FOUND\n";
    break;
  }
}

// nếu chưa tìm thấy thì quất tiếp vòng for này
for (unsigned int i = 0; i < (1<<27); ++i) {
  int c[10] = {0}; // xài c[1] tới c[9], bỏ c[0]
  bool validConfig = true;
  int totalCount = 0;
  int sum = 0;
  for (int j = 1; j <= 9; ++j) {
    // trích c[j] từ i
    c[j] = (i >> (3*j - 3)) & 7);
    if (c[j] > count(j)) {
      validConfig = false;
      break;
    }
    // update totalCount: thêm c[j] vào tổng số
    totalCount += c[j];
    if (minCount > totalCount || totalCount > maxCount) {
      validConfig = false;
      break;
    }
    // update sum
    sum += j * c[j];
  }
  if (validConfig && sum % 10 == m) {
    std::cout << "FOUND\n";
    break;
  }
}
3 Likes

Đầu tiên em cám ơn bác :smiley: nhưng thực sự đọc thấy rất khó hiểu và em chưa hình dung được cách chạy của thuật toán cụ thể là như nào ạ! Bác có thể nói rõ hơn không ạ?
Em đọc thì em hiểu rằng bác giả sử ta đã có 1 tập con của tập đã cho có t phần tử (len mà bác đề cập) và hàm f là hàm thể hiện giá trị của tổng tập con t phần tử này sau khi % 10. Sau khi thêm 1 phần tử thì thành tập mới có t + 1 phần tử là cách tính như bác đã đưa. Thì ở đây em thấy được sự gối nhau giữa các bài toán con. Nhưng mà lúc đầu khởi tạo như nào để đi từ tập con có 1 đến t và việc thêm 1 phần tử vào tập như nào thì em chưa hình dung được ạ :sweat_smile: :sweat_smile: :sweat_smile:

em cám ơn bác nghe :smiley: đọc phải ngẫm lắm em mới ngộ ra vài thứ mà vẩn hơi mơ hồ chắc phải ngẫm thêm :sweat_smile:
mà hình như cách này nó chỉ tối ưu hơn vét cạn bình thường khi k và n lớn phải không ạ? vd như k = 3, n = 40 thì vét cạn bt là 40C3 = 9880 < cách này thì 3^9 = 19683

Bạn có thể đăng link nộp bài lên đây được không?

link thì không có ạ. vì cái này là cái đề của bữa em đi phòng vấn, làm test trên máy cty họ luôn. :sweat_smile:

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