ICPC Competitive Programming

Z-Function

Z-Function สำหรับ Pattern Matching และปัญหา String

Z-Function

Z-Function (หรือ Z-Array) เป็น algorithm ที่คำนวณ array zz โดยที่ z[i]z[i] = ความยาวของ substring ที่ยาวที่สุดเริ่มจาก s[i]s[i] ที่เป็น prefix ของ ss

นิยาม

สำหรับ string ss ความยาว nn:

z[i]=max{k:s[0..k1]=s[i..i+k1]}z[i] = \max\{k : s[0..k-1] = s[i..i+k-1]\}

โดยที่ z[0]=0z[0] = 0 (หรือบางที่นิยามเป็น nn)

ตัวอย่าง

String: "aabxaab"

is[i..]z[i]Explanation
0aabxaab0(undefined/0)
1abxaab1”a” = s[0]
2bxaab0”b” != “a”
3xaab0”x” != “a”
4aab3”aab” = s[0..2]
5ab2”ab” = s[0..1]
6b0”b” != “a”

Algorithm

ใช้ concept ของ Z-Box คือ interval [l,r][l, r] ที่ s[l..r]s[l..r] = prefix ของ ss

vector<int> zFunction(const string& s) {
    int n = s.size();
    vector<int> z(n, 0);
    
    int l = 0, r = 0;  // Z-box [l, r]
    
    for (int i = 1; i < n; i++) {
        if (i < r) {
            // ใช้ค่าที่คำนวณไว้แล้ว
            z[i] = min(r - i, z[i - l]);
        }
        
        // Extend z[i] ด้วย naive comparison
        while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
            z[i]++;
        }
        
        // Update Z-box
        if (i + z[i] > r) {
            l = i;
            r = i + z[i];
        }
    }
    
    return z;
}

Visualization

s = "aabxaab"
    0123456

i=1: s[1..] = "abxaab"
     Compare with s[0..] = "aabxaab"
     "a" matches, "b" != "a"
     z[1] = 1

i=4: s[4..] = "aab"
     Compare with s[0..] = "aab..."
     "aab" matches
     z[4] = 3

ประยุกต์ใช้งาน

1. Pattern Matching

ต่อ pattern กับ text: pattern + "$" + text

vector<int> zMatch(const string& text, const string& pattern) {
    string combined = pattern + "$" + text;
    vector<int> z = zFunction(combined);
    
    vector<int> matches;
    int m = pattern.size();
    
    for (int i = m + 1; i < combined.size(); i++) {
        if (z[i] == m) {
            matches.push_back(i - m - 1);
        }
    }
    
    return matches;
}

2. หา Period ของ String

int findSmallestPeriod(const string& s) {
    vector<int> z = zFunction(s);
    int n = s.size();
    
    for (int p = 1; p < n; p++) {
        // p เป็น period ถ้า z[p] = n - p
        if (z[p] == n - p && n % p == 0) {
            return p;
        }
    }
    
    return n;
}

3. นับ Distinct Substrings

long long countDistinctSubstrings(const string& s) {
    string current = "";
    long long total = 0;
    
    for (char c : s) {
        current = c + current;  // prepend
        vector<int> z = zFunction(current);
        
        int maxZ = 0;
        for (int i = 1; i < z.size(); i++) {
            maxZ = max(maxZ, z[i]);
        }
        
        total += current.size() - maxZ;
    }
    
    return total;
}

4. String Compression

หา representation สั้นที่สุดของ string ที่เป็นการซ้ำของ pattern

string compress(const string& s) {
    vector<int> z = zFunction(s);
    int n = s.size();
    
    for (int len = 1; len <= n; len++) {
        if (n % len == 0) {
            bool valid = true;
            for (int i = len; i < n; i += len) {
                if (z[i] < len && z[i] != n - i) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                return s.substr(0, len) + " x " + to_string(n / len);
            }
        }
    }
    
    return s;
}

เปรียบเทียบ Z-Function กับ KMP

AspectZ-FunctionKMP (Prefix Function)
DefinitionMatch with prefix from position iLongest proper prefix = suffix
Value at iLength of match starting at iLength of matching prefix-suffix
DirectionForward matchingBackward reference
Use casePattern at startPattern at end of current

Complexity

OperationTimeSpace
Build Z-arrayO(n)O(n)O(n)O(n)
Pattern searchO(n+m)O(n + m)O(n+m)O(n + m)

Tips

  1. เลือกใช้ Z หรือ KMP - ทั้งสองทำได้คล้ายกัน แต่บางปัญหาอาจง่ายกว่าด้วยอันใดอันหนึ่ง
  2. Sentinel character - ใช้ตัวคั่นที่ไม่อยู่ใน alphabet
  3. Z-box optimization - ทำให้ algorithm เป็น linear time