Note - Border Theory

经典结论,但是不太会就写一个博客。

结论

给定字符串 \(s\),其所有 Border 可以按二进制最高位划分为 \(\mathrm O(\log n)\) 个等差数列。

证明

下将字符串长度记为 \(n\)

定义 \(i\) 为字符串 \(s\) 的 Border,当且仅当 \(\forall 1 \le p \le i, s_p = s_{n-i+p}\)。故 Border 又称公共前后缀。

定义 \(i\) 为字符串的周期,当且仅当 \(\forall 1 \le p \le n-i, s_p = s_{i+p}\)

引理 1:若 \(i\)\(s\) 的一个 Border,则 \(n-i\)\(i\) 的一个周期。

证明:由于 \(i\) 是一个 Border,则 \(\forall 1 \le p \le i, s_p = s_{n-i+p}\),变形得 \(1 \le p \le n-(n-i), s_p = s_{p+(n-i)}\),发现与周期的定义形式相同。证毕。

引理 2(弱周期定理):若 \(p, q\) 都是 \(s\) 的周期,且 \(p+q \le n\),则 \(\gcd(p, q)\) 也是 \(s\) 的周期。

证明:不妨设 \(p > q\),考虑 \(i[1 \le i \le n-(p-q)]\)

  • \(i \le n-p\),则由 \(p\) 为周期有 \(s_i = s_{i+p}\),由 \(q\) 为周期有 \(s_{i+p} = s_{i+p-q}\),联合上式得 \(s_{i} = s_{i+p-q}\)
  • \(i > n-p\),由 \(p+q \le n\)\(i > q\)。则仿照上式,有 \(s_{i} = s_{i-q}\)\(s_{i-q} = s_{i-q+p}\),则有 \(s_{i} = s_{i+p-q}\)

感性地,我们可以把下标左右移动 \(p\) 或者 \(q\),前提是不超出 \([1, n]\)。那么在给出的条件内,\(i+p-q\)\(i-q+p\) 必有一个可行。

发现上过程实际上是辗转相减,于是证毕。

引理 2.5(强周期定理):若 \(p, q\) 都是 \(s\) 的周期,且 \(p+q-\gcd(p, q) \le n\),则 \(\gcd(p, q)\) 也是 \(s\) 的周期。

证明:Hootime 看了 2h,证明了自己不会证明。可以看这个。但是没关系反正后面用不着。

引理 3:对于所有 \(\ge \lfloor \frac n 2 \rfloor\) 的 Border,其与 \(n\) 组成等差数列。

若只有 \(0\)\(1\)\(\ge \lfloor \frac n 2 \rfloor\) 的 Border,定理显然成立。

考虑两个 \(\ge \lfloor \frac n 2 \rfloor\) 的 Border \(n-p, n-q\),由 引理 1 我们得到两个周期 \(p, q\)。又由 引理 2 我们得到 \(\gcd(p, q)\) 也是一个周期。

由于 \(n-p\) 是最大 Border,则 \(p\) 必然是最小周期,则 \(\gcd(p, q) = p\),也就是说 \(q\)\(p\) 的倍数。推广到所有满足条件的 \(q\) 即得证。

对于结论,我们考虑 \([2^i, 2^{i+1})\),设其中最大的 Border 为 \(p_i\),显然其余的 Border 都是 \(s_{1 \sim p}\) 的 Border,由 引理 3 得其为等差数列。证毕。

例题

P4156 [WC2016] 论战捆竹竿

容易发现接在一起的一段必然是 Border。

假设我们不知道 Border Theory。我们发现可以对于每一个 Border \(i\),对于 \(n-i\) 跑同余最短路,但是 \(\mathrm O(n^2)\),视实现方式 \(30 \sim 50 \mathrm{pts}\)

然而我们知道 Border Theory。

知道 Border Theory 我们就可以把 Border 拆解为 \(\log n\) 个等差数列,然后对于每个等差数列 \(a_i = a_0+ki\),我们以 \(a_0\) 为模数跑一遍同余最短路,使用单调队列求当前的最短长度即可。

接下来我们考虑如何把同余最短路的模数从 \(a\) 换成 \(b\)。我们可以把 \(a\) 中每个同余类的答案 \(dis_i\) 转移到 \(dis'_{dis_i \bmod b}\)。然而存在情况使得在 \(a\) 的剩余系中不优的距离在 \(b\) 的剩余系中最优。发现这些情况一定可以由 \(a\) 剩余系中最优的距离加上若干个 \(a\) 求得,于是我们再这么跑一遍同余最短路即可。

#include <bits/stdc++.h>
#define llong long long
#define N 500005
using namespace std;

int n; llong k;
char a[N];
int nxt[N], Log2[N];
basic_string<int> num[N];
llong dis[N], tmp[N], ans;

typedef pair<int, llong> Node;
Node que[N<<1]; int he, ta;

void _prework(){
    for(int i = 2; i < N; ++i) Log2[i] = Log2[i>>1]+1;
}

void _init(){
    for(int i = 0; i <= Log2[n]; ++i) num[i].clear();
    ans = 0;
    return;
}

int _main(){
    scanf("%d %lld", &n, &k), k -= n;
    scanf("%s", a+1);
    if(k < 0){
        puts("0");
        return 0;
    }
    for(int i = 2; i <= n; ++i){
        nxt[i] = nxt[i-1];
        while(nxt[i] && a[nxt[i]+1] != a[i]) nxt[i] = nxt[nxt[i]];
        if(a[nxt[i]+1] == a[i]) ++nxt[i];
    }
    int x = n;
    while(x){
        x = nxt[x];
        num[Log2[x]].push_back(n-x);
    }
    for(int i = 1; i < N; ++i) dis[i] = (llong)1e18+3;
    int lst = 1e9+7;
    for(int i = 0; i <= Log2[n]; ++i){
        if(!num[i].size()) continue;
        int now = num[i][0], siz = num[i].size();
        if(lst < 1e9){
            for(int j = 0; j < now; ++j) tmp[j] = (llong)1e18+3;
            for(int j = 0; j < lst; ++j)
                tmp[dis[j]%now] = min(tmp[dis[j]%now], dis[j]);
            int l = __gcd(now, lst), len = now/l;
            for(int j = 0; j < l; ++j){
                int pos1 = j, pos2 = j+lst;
                for(llong k = 1; k <= len*2; ++k, pos1 = pos2, pos2 = pos2+lst){
                    while(pos2 > now) pos2 -= now;
                    tmp[pos2] = min(tmp[pos2], tmp[pos1]+lst);
                }
            }
            for(int j = 0; j < now; ++j) dis[j] = tmp[j];
        }
        lst = now;
        if(siz == 1) continue;
        int d = num[i][1]-num[i][0];
        int l = __gcd(now, d), len = now/l;
        for(int j = 0; j < l; ++j){
            que[he = ta = 1] = make_pair(j, 0);
            int pos = j+d;
            for(llong k = 1; k <= len*2; ++k, pos = pos+d){
                while(pos > now) pos -= now;
                while(he <= ta && que[he].second <= k-siz) ++he;
                dis[pos] = min(dis[pos], dis[que[he].first]+now+(k-que[he].second)*d);
                while(he <= ta && dis[que[ta].first]+(k-que[ta].second)*d >= dis[pos]) --ta;
                que[++ta] = make_pair(pos, k);
            }
        }
    }
    for(int i = 0; i < lst; ++i)
        if(dis[i] <= k) ans += (k-dis[i])/lst+1;
    printf("%lld\n", ans);
    return 0;        
}

int T;
int main(){
    scanf("%d", &T);
    _prework();
    while(T--) _init(), _main();
    return 0;
}

posted @ 2026-03-16 16:49  Hootime  阅读(2)  评论(0)    收藏  举报