Bài toán Chuỗi nhị phân

Chuỗi nhị phân

Bài toán Chuỗi nhị phân

Xét xâu nhị phân, tức là xâu chỉ chứa các ký tự trong tập {0, 1}. Gọi k là số lượng xâu nhị phân độ dài n (1 ≤ n ≤ 104) chứa xâu S (độ dài không quá 100) như một xâu con (các ký tự liên tiếp) đúng một lần.

Yêu cầu: Hãy tính phần dư của kết quả chia k cho 109+7.

Dữ liệu: Vào từ file văn bản BINARY.INP:

  • Dòng thứ nhất chứa số nguyên n,
  • Dòng thứ hai chứa xâu S.

Kết quả: Đưa ra file văn bản BINARY.OUT một số nguyên – kết quả tìm được.

Ví dụ: // p32     Ind9 _0103/2008A

BINARY.INP

BINARY.OUT

4

01

10

 

Thuật toán giải bài toán Chuỗi nhị phân

Bài toán liệt kê số lần xuất hiện của một xâu con S trong xâu nhị phân có thể được giải bằng nhiều thuật toán khác nhau. Sau đây là một số thuật toán thường được sử dụng để giải quyết bài toán này, cùng với độ phức tạp tương ứng:

  1. Thuật toán Brute Force: độ phức tạp O(n*m), với n là độ dài của xâu nhị phân và m là độ dài của xâu con S.
  2. Thuật toán Knuth-Morris-Pratt: độ phức tạp O(n+m), với n là độ dài của xâu nhị phân và m là độ dài của xâu con S.
  3. Thuật toán Rabin-Karp: độ phức tạp O(n+m), với n là độ dài của xâu nhị phân và m là độ dài của xâu con S.
  4. Thuật toán Boyer-Moore: độ phức tạp trung bình O(n/m), với n là độ dài của xâu nhị phân và m là độ dài của xâu con S.

Các thuật toán trên có độ phức tạp khác nhau và có thể phù hợp với các trường hợp sử dụng khác nhau. Nếu xâu con S có độ dài nhỏ, thì thuật toán Brute Force có thể được sử dụng. Nếu xâu con S có độ dài lớn hơn và có nhiều trường hợp tìm kiếm, thì thuật toán Knuth-Morris-Pratt hoặc Rabin-Karp có thể được sử dụng. Nếu xâu con S có độ dài lớn hơn và xuất hiện ít lần trong xâu nhị phân, thì thuật toán Boyer-Moore có thể được sử dụng.

Ngoài ra, có thể sử dụng thuật toán sinh nhị phân để giải quyết bài toán liệt kê số lần xuất hiện của một xâu con S trong xâu nhị phân. Tuy nhiên, độ phức tạp của thuật toán sinh nhị phân là O(2^n), với n là độ dài của xâu nhị phân, do đó không phù hợp cho các bộ dữ liệu lớn. Thuật toán sinh nhị phân là thuật toán tạo ra tất cả các xâu nhị phân có độ dài n bằng cách duyệt qua tất cả các trường hợp có thể của các ký tự 0 và 1. Sau đó, với mỗi xâu nhị phân được sinh ra, ta kiểm tra xem xâu con S có xuất hiện trong xâu nhị phân hay không. Nếu có, ta tăng biến đếm kết quả lên 1.

Vì vậy, nếu bộ dữ liệu lớn, chúng ta nên sử dụng các thuật toán khác, chẳng hạn như Knuth-Morris-Pratt, Rabin-Karp hoặc Boyer-Moore, để giảm độ phức tạp tính toán và thời gian chạy của chương trình.

Dưới đây là cài đặt của bài toán với thuật toán tối ưu nhất: Thuật toán Rabin-Karp

Cài đặt bài toán với Python

MOD = 1000000007

def rabin_karp(text, pattern):
    n = len(text)
    m = len(pattern)
    if n < m:
        return 0
    
    d = 256
    h = pow(d, m-1, MOD)
    
    p = 0
    t = 0
    for i in range(m):
        p = (d * p + ord(pattern[i])) % MOD
        t = (d * t + ord(text[i])) % MOD
        
    count = 0
    for i in range(n-m+1):
        if p == t:
            match = True
            for j in range(m):
                if pattern[j] != text[i+j]:
                    match = False
                    break
            if match:
                count += 1
        if i < n-m:
            t = (d * (t - ord(text[i]) * h) + ord(text[i+m])) % MOD
            t = (t + MOD) % MOD
            
    return count

with open('BINARY.INP', 'r') as f:
    n = int(f.readline().strip())
    S = f.readline().strip()

# Tính số xâu nhị phân có độ dài n
count = pow(2, n, MOD)

# Tìm số lần xuất hiện của S trong các xâu nhị phân có độ dài n
count_s = rabin_karp(bin(count-1)[2:], S)

# Tính kết quả
result = (count - count_s) % MOD

# Xuất kết quả ra file
with open("BINARY.OUT", "w") as f:
    f.write(str(result))

Cài đặt bài toán với C++

#include <bits/stdc++.h>
using namespace std;

const int MOD = 1e9 + 7;
const int BASE = 26;

int main() {
    freopen("BINARY.INP", "r", stdin);
    freopen("BINARY.OUT", "w", stdout);

    int n;
    string s;
    cin >> n >> s;

    int m = s.length();
    vector<int> p(n - m + 1);
    int h = 1;
    for (int i = 0; i < m; i++) {
        p[0] = (p[0] * BASE + s[i]) % MOD;
        if (i < m - 1) h = (h * BASE) % MOD;
    }
    for (int i = 1; i <= n - m; i++) {
        p[i] = (BASE * (p[i - 1] - s[i - 1] * h) + s[i + m - 1]) % MOD;
        if (p[i] < 0) p[i] += MOD;
    }

    int ans = 0;
    unordered_map<int, int> cnt;
    for (int i = 0; i <= n - m; i++) {
        cnt[p[i]]++;
        if (i >= m) cnt[p[i - m]]--;
        if (i >= m - 1 && cnt[p[i - m]] == 0) ans++;
    }

    cout << ans << endl;

    return 0;
}

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *