Hướng dẫn cho Ghép số


Chỉ sử dụng khi thực sự cần thiết như một cách tôn trọng tác giả và người viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là hành vi có thể dẫn đến khóa tài khoản.

Authors: SPyofgame , jumptozero


Spoiler Alert


Hint 1

  • Chỉ thứ tự các chữ số trong \(B\) không thay đổi trong \(C\)

Đọc kĩ đề nhé mấy bạn, số \(A\) mình có thể thay đổi vị trí bất kì đó

Ta sẽ tìm cách sắp xếp các chữ số \(A\) trong \(C\) sao cho phù hợp


Hint 2

  • Xét bài toán con: Giả sử mình đã có một cách sắp xếp các chữ số trong \(A\) phù hợp, và mình ghép tất cả chữ số của \(A\)\(B\) vào nhau sao cho nó lớn nhất

Mình sẽ dùng kĩ thuật \(two-pointers\) để di chuyển 2 con trỏ \(ptr_A\)\(ptr_B\) sang tới vị trí cuối cùng của 2 số và xuất ra sao cho tối ưu nhất

Khi này, mình có 2 hướng đi

Nếu \(ptr_A\) chưa ở cuối \(A\): xuất \(A[ptr_A]\) và di chuyển \(ptr_A\) sang phải

Nếu \(ptr_B\) chưa ở cuối \(B\): xuất \(B[ptr_B]\) và di chuyển \(ptr_B\) sang phải


Hint 3

  • Trong bài toán con trên: Ta sẽ ưu tiên chữ số lớn hơn được xuất trước

Nếu mình được chọn một trong hai số \(A[ptr_A]\)\(B[ptr_B]\) để xuất ra số lớn nhất.

Mà chữ số được xuất trước lớn hơn thì cả số đó sẽ lớn hơn.

Nên mình sẽ xuất \(max(A[ptr_A], B[ptr_B])\) và di chuyển con trỏ có giá trị \(max\) sang phải

Và cũng từ đây mình rút ra: \(A\) càng lớn thì số được xuất ra càng lớn.

Vậy ta sẽ sắp xếp các chữ số trong \(A\) giảm dần


Hint 4

  • Trường hợp còn lại cũng tương tự cho đến khi bạn nhận ra \(C\) không chứa các số \(0\) vô nghĩa ở đầu

Sau khi sắp xếp \(A\) nhỏ dần, ta có thể cho một trong 2 số sau lên đầu số \(C\)

Chữ số đầu tiên nhỏ nhất khác \(0\) của \(A\)

Chữ số đầu tiên của \(B\) (phải khác \(0\) và không được chọn vị trí khác)

Vậy chữ số nào bé hơn và khác \(0\) thì ta sẽ chọn

Mà chữ số đầu của \(C\) theo cách chọn tối ưu trên sẽ không phụ thuộc cách sắp xếp \(A\)

Nên ta có swap vị trí chữ số bé nhất khác \(0\) của \(A\) lên đầu khi nó bé hơn chữ số đầu của \(B\)


Reference AC code | \(O(n \log_2 n)\) time | \(O(n)\) auxiliary space | Two-pointers, String

C++
int main()
{
    string s; /// so A bieu dien kieu string
    cin >> s;

    string t; /// so B bieu dien kieu string
    cin >> t;

    sort(all(s)); /// sap xep giam dan
    if (s.front() == '0') /// chu so dau A la so 0
    {
        for (char &c : s) /// tim trong A phan tu dau khac 0
        {
            if (c != '0') /// da tim ra phan tu do
            {
                if (c < t.front()) /// kiem tra xem no co nho hon chu so dau cua B khong
                {
                    swap(c, s.front()); /// neu co thi swap len A
                    break;              /// ta khong can tim them nua
                }
            }
        }
    }

    int n = s.size(), m = t.size();
    for (int ps = 0, pt = 0; ps < n && pt < m; ) /// 2 con tro ps, pt
    {
        if (s[ps] < t[pt] && (ps != 0 || pt != 0 || s[ps] != '0')) /// loai bo phan tu leading_zero
             cout << s[ps++]; /// Xuat s[ps] va di chuyen con tro sang phai
        else cout << t[pt++]; /// Xuat t[pt] va di chuyen con tro sang phai  
        if (pt == m) while (ps < n) cout << s[ps++]; /// Da in het B, nen xuat het A
        if (ps == n) while (pt < m) cout << t[pt++]; /// Da in het A, nen xuat het B
    }
    cout << eol;

    sort(all(s), greater<char>()); /// sap xep tang dan
    for (int ps = 0, pt = 0; ps < n && pt < m; ) /// 2 con tro ps, pt
    {
        if (s[ps] > t[pt]) /// chon phan tu co gia tri lon hon
             cout << s[ps++]; /// Xuat s[ps] va di chuyen con tro sang phai
        else cout << t[pt++]; /// Xuat t[pt] va di chuyen con tro sang phai  
        if (pt == m) while (ps < n) cout << s[ps++]; /// Da in het B, nen xuat het A
        if (ps == n) while (pt < m) cout << t[pt++]; /// Da in het A, nen xuat het B
    }
    return 0;
}

jumptozero Editorial

  • Mình xin chia sẻ lời giải bài toán này như sau:
  • Đầu tiên ta có chú ý đó là: Vì \(C\) được tạo từ các chữ số của \(A\)\(B\)\(C\)\(N+M\) chữ số. Nên nếu \(C\) có số \(0\) đứng đầu thì xem như số đó không thỏa mãn , vì nó không được tính là \(N+M\) chữ số.
  • Ví dụ: \(A=10,B=1\). Thì số \(C=011\) là không thỏa mãn yêu cầu bài toán vì số chữ số thực sự của nó chỉ là \(2\ne 3\).
  • Tiếp theo ta đến phần tìm \(C\) nhỏ nhất.
  • Vì thứ tự của chuỗi \(A\) ta có thể tùy ý thay đổi, còn chuỗi \(B\) thì không, do đó để số \(C\) nhỏ nhất thì ta làm một việc đó là :
  • Ta sẽ sắp xếp lại chuỗi \(A\) thỏa mãn tính chất sau:
  • Nếu trong chuỗi \(A\) không có kí tự nào bằng \(0\) thì ta sẽ sắp xếp chuỗi theo tự tăng dần, tức là \(A[0],A[1],...,A[N-1]\)(trong đó \(A[0]<=A[1]<=...<=A[N-1]\))
  • Nếu trong chuỗi \(A\) tồn tại kí tự bằng \(0\) thì ta sẽ sắp xếp chuỗi theo dạng sau: \(s,0,0,...,A[i],A[i+1],...,A[N-1]\) (trong đó s là kí tự nhỏ nhất khác '0' , còn các phần tử còn lại ta sắp xếp theo tự tăng dần).
  • Ví dụ: Giả sử có có \(A=36002102\). Thì khi đó ta sắp xếp lại \(A\) như sau: \(A=10002236\).
  • Việc sắp xếp như vậy, sẽ giúp ta dễ dàng tìm được \(C\) thông qua việc sử dụng một vòng while và hai con trỏ tương ứng với hai chuỗi \(A\)\(B\).
  • Ý tưởng của vòng lặp rất đơn giản:
  • Ta so sánh \(A[i]\)\(B[j]\) để tìm ra số nhỏ nhất, và cộng vào chuỗi \(res\) (chuỗi \(res\) khởi tạo ban đầu là rỗng).
  • Nếu \(A[i]>B[j]\) thì ta cộng \(B[j]\) vào \(res\), ngược lại nếu \(A[i]<B[j]\) thì ta cộng \(A[i]\) vào \(res\).
  • Điểm hay của bài toán này đó là khi \(A[i]=B[j]\) thì ta chọn \(A[i]\) hay \(B[j]\) để cộng vào \(res\) ???
  • Để giải quyết nút thắt này, ta cần phải so sánh \(A[i+1]\)\(B[j+1]\).
  • Các bạn có thể xem đoạn xử lý dưới đây:
    C++
    if(s1[i]<s2[j]) {res+=s1[i];i++;}
                else if(s1[i]>s2[j]){res+=s2[j];j++;}
                else {
                    if(s1[i+1]<s2[j+1]&&i+1<s1.size()&&j+1<s2.size()) {res+=s1[i];i++;continue;}
                    if(s1[i+1]>s2[j+1]&&i+1<s1.size()&&j+1<s2.size()) {res+=s2[j];j++;continue;}
                    if(i+1==s1.size()||j+1==s2.size()) {res+=s2[j];j++;continue;}
                    if(s1[i+1]==s2[j+1]&&i+1<s1.size()&&j+1<s2.size()) {res+=s2[j];j++;continue;}
                }
                continue;
    
  • Như vậy ta đã giải quyết xong phần tìm \(C\) nhỏ nhất,
  • Tiếp theo đến phần tìm \(C\) lớn nhất. Phần này tương tự phần tìm \(C\) nhỏ nhất !
  • Như vậy là bài toán đã giải quyết xong.
  • Các bạn có thể tham khảo code của mình tại \(\href{https://ideone.com/5EhCY2}{đây}\)
  • Nếu có gì sai hoặc khó hiểu, các bạn cứ comment nhé!

  • P/s: Code mình hơi xấu !



Bình luận

Không có bình luận nào.