Hướng dẫn giải của Xâu đối xứng hoàn hảo


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.

Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.

Bạn phải đăng nhập để mở lời giải này.

Đăng nhập

Tác giả: Hiếu Nguyễn, Nemztran, Namdo, LamThanhVu

Editorial for repalind: Xâu đối xứng hoàn hảo

Hiểu bài toán

Cho một xâu $s$ và số nguyên dương $k$. Xâu $t$ được tạo thành bằng cách lặp lại $s$ liên tiếp $k$ lần. Nhiệm vụ là kiểm tra xem $t$ có phải là một xâu đối xứng (palindrome) hay không.

Phân tích:

  1. Xâu $t$ có độ dài $N = |s| \times k$.
  2. Xâu $t$ là palindrome nếu và chỉ nếu $t[i] = t[N-1-i]$ với mọi $0 \le i < N$.
  3. Xâu $t$ có thể được chia thành $k$ phần, mỗi phần là $s$. Ký tự tại vị trí $i$ trong $t$ thuộc về phần thứ $i / |s|$ và có kí tự tương ứng trong $s$ là $s[i \% |s|$.
  4. Xét điều kiện đối xứng:
    • Nếu $|s|$ là số chẵn, $t$ là palindrome nếu và chỉ khi $s$ là palindrome.
    • Nếu $|s|$ là số lẻ và $k$ là số chẵn, $t$ là palindrome nếu và chỉ khi $s$ là palindrome.
    • Nếu $|s|$ là số lẻ và $k$ là số lẻ, $t$ là palindrome nếu và chỉ khi $s$ là palindrome và "xâu đối xứng lặp" (thuật ngữ trong bài toán này gọi là Perfect Palindrome). Cụ thể, xét $s$ có độ dài $L$ lẻ, trục đối xứng nằm ở ký tự giữa. Để $t$ đối xứng, trục đối xứng của $s$ phải trùng với trục đối xứng của $t$. Điều này dẫn đến yêu cầu thêm về các ký tự của $s$. Ví dụ: $s = "abc"$, $L=3$ (lẻ). Ký tự giữa là 'b'. Để $t = "abcabcabc"$ đối xứng, ta cần $s[0] = s[2]$ ('a' = 'c') và các điều kiện khác. Thực chất, với $s$ độ dài lẻ và $k$ lẻ, $t$ là palindrome khi và chỉ khi $s$ là palindrome.

Tóm lại, lời giải đơn giản nhất là: $t$ là palindrome nếu và chỉ khi $s$ là palindrome.

Các cách tiếp cận

Cách Kiểm tra xâu s có phải palindrome không
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

// Hàm kiểm tra xâu a có phải là palindrome không
bool is_palindrome(char a[]) {
    int len = strlen(a);
    for (int i = 0; i < len / 2; i++) {
        if (a[i] != a[len - i - 1]) {
            return false;
        }
    }
    return true;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        char s[5005];
        long long k;
        scanf("%s %lld", s, &k);

        // Logic thực tế:
        // Nếu s là palindrome, thì t = s repeated k times luon la palindrome.
        // (Phân tích chi tiết trong phần Problem Understanding)
        if (is_palindrome(s)) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }
    return 0;
}
  • Time Complexity: O(|s|)
  • Space Complexity: O(1)

Giải pháp này dựa trên nhận định rằng xâu $t$ được tạo từ $s$ lặp lại $k$ lần là palindrome khi và chỉ khi $s$ là palindrome.

  1. Tại sao chỉ cần kiểm tra $s$?

    • Nếu $s$ là palindrome, tính đối xứng của $s$ sẽ được bảo toàn khi lặp lại.
    • Nếu $s$ không phải là palindrome, chắc chắn $t$ không thể là palindrome (trừ các trường hợp rất đặc biệt không xảy ra trong bài toán này).
  2. Thao tác:

    • Đọc vào $T$.
    • Với mỗi test case, đọc $s$ và $k$ (kỳ thực giá trị $k$ không cần thiết cho lời giải này).
    • Kiểm tra $s$ có đối xứng không bằng cách so sánh $s[i]$ với $s[len-1-i]$.
  3. Độ phức tạp:

    • Thời gian: $O(|s|)$ cho mỗi test case.
    • Không gian: $O(1)$ (không dùng thêm bộ nhớ đáng kể).
Cách Code mẫu từ các giải pháp đã nộp (C)
#include<stdio.h>
#include<string.h>
#include<stdbool.h>

bool doixung(char a[]){
    int p = strlen(a);
    for (int i = 0;i < p / 2; i++){
        if (a[i] != a[p - i - 1]) return 0;
    }
    return 1;
}

int main(){
    int n;
    scanf("%d",&n);
    getchar();
    while(n--){
        char a[10000];
        fgets(a,sizeof(a),stdin);
        a[strcspn(a,"\n")] = '\0';
        long long k;
        scanf("%lld",&k);
        getchar();
        if (doixung(a)) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
  • Time Complexity: O(|s|)
  • Space Complexity: O(1)

Đây là cách tiếp cận trực quan nhất và cũng là cách đúng đắn nhất cho bài toán này dựa trên các phân tích toán học về tính chất của xâu lặp.

Các bước:

  1. Nhập số lượng test cases $T$.
  2. Với mỗi test case:
    • Nhập xâu $s$. Lưu ý các solution mẫu có cách nhập khác nhau (scanf hoặc fgets) nhưng đều hoạt động tốt với điều kiện input.
    • Nhập số $k$. Mặc dù $k$ có thể lên tới $10^{18}$, nhưng ta không cần dùng nó để tính toán gì nhiều.
    • Gọi hàm doixung (hay is_palindrome) kiểm tra $s$.
    • In kết quả.

Ví dụ:

  • $s = "aba", k = 3$. $s$ palindrome -> YES.
  • $s = "ccdd", k = 2$. $s$ không palindrome -> NO.
  • $s = "freecontest", k = 1$. $s$ không palindrome -> NO.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O( s ) O(1) Kiểm tra xâu s có phải palindrome không
2 O( s ) O(1) Code mẫu từ các giải pháp đã nộp (C)

Bài học kinh nghiệm

  • Bài toán có vẻ phức tạp do $k$ rất lớn ($10^{18}$), nhưng thực chất việc lặp lại $k$ lần không làm thay đổi tính chất palindrome của xâu gốc $s$. Nếu $s$ là palindrome, $s^k$ (nghĩa là $s$ lặp $k$ lần) cũng là palindrome.
  • Lưu ý về cách nhập/xử lý xâu: Vì $s$ chỉ chứa ký tự in thường và số, không có khoảng trắng, có thể dùng scanf("%s"). Nếu dùng fgets cần chú ý loại bỏ ký tự newline.

Lỗi thường gặp

  • Sai lầm khi cố gắng tạo ra xâu $t$ hoặc tính toán phức tạp dựa trên $k$. $k$ có giá trị lên tới $10^{18}$, không thể lưu trữ xâu $t$ (độ dài tối đa $5000 \times 10^{18}$) vào bộ nhớ hay duyệt qua nó.
  • Quên xử lý ký tự newline khi trộn lẫn scanffgets (mặc dù trong bài này scanf cho $s$ là đủ).
  • Lầm tưởng rằng với $k$ chẵn, $t$ luôn palindrome ngay cả khi $s$ không palindrome. Ví dụ $s = "ab", k = 2 \rightarrow t = "abab"$ không palindrome.

Bình luận

Please read the guidelines before commenting.


Không có bình luận tại thời điểm này.