L. Lazy Printing (kmp/sa/暴力)----2022-2023 ACM-ICPC Latin American Regional Programming Contest

文章描述了一个关于字符串处理的问题,Vinícius有一台特殊的打字机,需要通过特定的指令序列打印出目标字符串。每个指令包含一个字符串和一个正整数D,表示用该字符串的循环节最多重复D次来打印字符。问题在于找到打印给定字符串所需的最少指令数。文章提供了几种不同的解决方案,包括贪心算法和使用后缀数组计算最长公共前缀等方法。
摘要由CSDN通过智能技术生成
L. Lazy Printing

https://codeforces.com/gym/104252/problem/C

Vinícius has an interesting typing machine. The machine accepts instructions consisting of a non-empty string s and a positive integer n. For each such instruction, the machine prints n characters: the i-th (0-based) printed character equals s_r, where r is the remainder after dividing i by the length of s and s_r denotes the r-th (0-based) character of s. For instance, with the sequence of instructions:

  1. s=“ab”, n=4
  2. s=“cd”, n=3
  3. s=“xx”, n=2

the machine will print “ababcdcxx”.

Vinícius is lazy, so he only gives strings of length at most D to the machine in each instruction. Since he is very lazy, he also wants to use as few instructions as possible. Given a string T and the integer D, help Vinícius find the minimum number of instructions he needs in order to print T using the machine.

Input

The input consists of a single line that contains a string T of lowercase letters followed by the integer D ( 1 ≤ D ≤ ∣ T ∣ ≤ 2 × 1 0 5 ) (1 \leq D \leq |T| \leq 2 \times 10^5) (1DT2×105), as described in the statement.

Output

Output a single line with an integer indicating the minimum number of instructions Vinícius needs.

Examples

input

ababcdcxx 2

output

3

input

aaabbcd 1

output

4

input

abcabca 3

output

1

题意:给一个总字符串,然后把他拆成若干个子串,每个子串由自己的循环节循环得来(不一定是完整周期),题目要求循环节的长度最长是d,求最少拆成几个子串。比如样例三,abc作为循环节,循环得到abcabca,就是原串,所以答案是1,样例1:a作为循环节循环三次变成aaa,b作为循环节循环两次变为bb,c,d作为循环节只循环一次得到c,d,组合起来就是aaabbcd,答案是4.

本题主要问题是贪心思想,方法有很多;只要能证明,从 0... l e n − 1 0...len-1 0...len1枚举,某个长度不大于d的循环节循环得到的最长子串就是拆分的局部最优解。也就是从左到右枚举s[i],从i开始再枚举循环节的长度k(范围1到d),再找 s [ i , l e n − 1 ] s[i,len-1] s[ilen1] s [ i + k , l e n − 1 ] s[i+k,len-1] s[i+k,len1]的最长公共前缀lcp,此时这个子串的长度就是 k + l c p k+lcp k+lcp的长度。

暴力做法:直接暴力拓展

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s;
    int d;
    cin >> s >> d;
    int len = s.size();
    int pos = 0;
    int ans = 0;
    while (pos < len) {
        int maxn = d;
        for (int k = 1; k <= d; k++) {
            int i = pos;
            while (s[i] == s[i + k] && i < len)
                i++;
            maxn = max(maxn, i - pos + k);
            // cout << maxn << endl;
        }
       //cout << maxn << " " << pos << endl;
        ans++;
        pos = pos + maxn;
    }
    cout << ans << endl;
}

SA:后缀数组的height数组可以直接得到两个串的lcp

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;

struct SA {
    char s[N];
    int sa[N], cnt[N], t1[N], t2[N], rk[N], height[N];
    int n;
    void build_sa() {
        int m = 128;  // 字符集大小,这里是ASCII码,如果是小写字母,就是26
        int *x = t1, *y = t2;
        n++;
        for (int i = 0; i < m; i++)
            cnt[i] = 0;  // 初始化 cnt数组
        // 进行第一轮计数排序
        for (int i = 0; i < n; i++)
            cnt[x[i] = s[i]]++;
        for (int i = 1; i < m; i++)
            cnt[i] += cnt[i - 1];
        for (int i = n - 1; i >= 0; i--)
            sa[--cnt[x[i]]] = i;

        for (int k = 1; k <= n; k <<= 1) {
            int p = 0;
            // 进行对第二关键字基数排序
            for (int i = n - k; i < n; i++)
                y[p++] = i;
            for (int i = 0; i < n; i++)
                if (sa[i] >= k) y[p++] = sa[i] - k;
            // 进行对第一关键字基数排序
            for (int i = 0; i < m; i++)
                cnt[i] = 0;
            for (int i = 0; i < n; i++)
                cnt[x[y[i]]]++;
            for (int i = 1; i < m; i++)
                cnt[i] += cnt[i - 1];
            for (int i = n - 1; i >= 0; i--)
                sa[--cnt[x[y[i]]]] = y[i];
            swap(x, y);  // 交换x和y
            p = 1;
            x[sa[0]] = 0;
            for (int i = 1; i < n; i++)
                x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
            if (p >= n) break;
            m = p;
        }
        int k = 0;
        n--;
        for (int i = 0; i <= n; i++)
            rk[sa[i]] = i;
        for (int i = 0; i < n; i++) {
            // if(rk[i] == 0) continue;
            if (k) k--;
            int j = sa[rk[i] - 1];
            while (s[i + k] == s[j + k])
                k++;
            height[rk[i]] = k;
        }
    }
    // void get_height() {
    // }
    int RMQ[N][20], Log[N];
    void initRMQ(int n) {
        for (int i = 2; i <= n; i++) {
            Log[i] = Log[i >> 1] + 1;
        }
        for (int i = 1; i <= n; i++)
            RMQ[i][0] = height[i];
        for (int j = 1; (1 << j) <= n; j++)
            for (int i = 1; i + (1 << j) - 1 <= n; i++)
                RMQ[i][j] = min(RMQ[i][j - 1], RMQ[i + (1 << (j - 1))][j - 1]);
    }

    int query(int l, int r) {
        int k = Log[r - l + 1];
        return min(RMQ[l][k], RMQ[r - (1 << k) + 1][k]);
    }

    int lcp(int x, int y) {
        x = rk[x], y = rk[y];
        if (x > y)
            swap(x, y);
        return query(x + 1, y);  // 返回sa[x]和sa[y]的最长公共前缀
    }

} sa_;
int main() {
    int d;
    cin >> sa_.s >> d;
    sa_.n = strlen(sa_.s);
    sa_.build_sa();
    sa_.initRMQ(sa_.n);

    int ans = 0;
    for (int i = 0; i < sa_.n;) {
        int maxn = d;                                    // 前串长度
        for (int k = 1; k <= d && i + k < sa_.n; k++) {  // 枚举前串长度
            maxn = max(maxn, k + sa_.lcp(i, i + k));  // 更新最大长度
        }
        ans++;
        i += maxn;
    }
    cout << ans << endl;
}

kmp:kmp可以直接得到最小循环节的大小(代码来自cf)

string t;
int d, n;
int kmp[N];

int calc(int start) {
    kmp[start] = start;
    for (int i = start + 1, j = start; i < n; i++) {
        while (j > start && t[i] != t[j])j = kmp[j - 1];
        j += (t[i] == t[j]);
        kmp[i] = j;
        if (i - kmp[i] >= d)
            return i;
    }
    return n;
}

void doWork() {

    cin >> t >> d;
    n = t.size();
    int ans = 0;
    for (int i = 0; i < n; i = calc(i)) {
        ans += 1;
    }
    cout << ans << '\n';

}

int32_t main() {
#ifdef ONLINE_JUDGE
    ios_base::sync_with_stdio(0);
    cin.tie(0);
#endif // ONLINE_JUDGE
    int t = 1;
//    cin >> t;
    while (t--)
        doWork();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值