字符串算法 金策_「WC2016」论战捆竹竿

「WC2016」论战捆竹竿

前置知识

参考资料:《论战捆竹竿解题报告—王鉴浩》,《字符串算法选讲—金策》。

Border&Period

若前缀 \(pre(s,x)​\) 与后缀 \(suf(s,n-x-1)​\) 相等,则 \(pre(s, x)​\) 是 \(s​\) 的一个 \(\text{Border}​\)。

\(x​\) 是 \(s​\) 的一个周期 (\(\text{Preiod}​\)) 满足 \(s[i]=s[i+x],\forall{1\leq i\leq|s|-x}​\) 。

对于一个 \(\text{Border } pre(s, x)​\) ,满足 \(|s|-x​\) 是 \(s​\) 的一个周期。

若 \(s​\) 最长的 \(Boder​\) 为 \(pre(s, x)​\) ,则 \(s​\) 的 \(\text{Border}​\) 数量为 \(pre(s, x)​\) 的 \(\text{Border}​\) 数量 \(+1​\) 。

Periodicity Lemma

若 \(p, q​\) 是 \(s​\) 的周期,且满足 \(p+q+\gcd(p,q)\leq |s|​\) ,则 \(gcd(p,q)​\) 也是 \(s​\) 的一个周期。

Borders 的性质

引理:对于长度 \(\geq​\) \(\frac{|s|}{2}​\) 的 \(s​\) 的 \(\text{Border}​\) ,其长度组成一个等差数列。

证明:假设有满足条件的 \(\text{Boder}​\) 集合 \(a,\forall a_i \geq \frac{|s|}{2}​\) ,可以得到周期集合 \(b, \forall b_i\leq \frac{|s|}{2}​\) 。

根据 Periodicity Lemma,\(b\) 的所有元素的 \(\gcd\) 也是 \(s\) 的一个周期,显然 \(a\) 集合能组成公差为 \(g\) 的等差数列。

推论:字符串 \(s\) 的所有 $\text{Border} $ 可以分成 \(O(\log|S|)\) 段等差数列。

根据引理, \(s​\) 所有长度 \(\geq \frac{|s|}{2}​\) 的 \(\text{Border}​\) 可以分成一个等差数列 ,设 \(s​\) 最长的不超过 \(\frac{|s|}{2}​\) 的 \(\text{Border}​\) 为 \(pre(s, m)​\) ,记 \(T(n)​\) 为 \(pre(s, n)​\) 的 \(\text{Border}​\) 分成的等差数列数量,显然有

\[T(n)\leq T(m)+ 1, T(n)=O(\log n)

\]

同理,字符串所有周期也可以被分成 \(O(\log|S|)\) 段等差数列。

解题思路

先转化一下题意,一开始字符串长度为 \(n\) ,每次可以接上初始字符串的一个周期或者初始字符串本身,求在 \(w\) 范围内能得到的字符串的长度种类数。

先不考虑 Border&Period 的性质,可以直接转化为一个模 \(n\) 意义下的最短路问题,复杂度 \(O(n^2)\) 。

由 前置知识 可以得知,每次加的长度可以分成 \(O(\log n)\) 段等差数列,考虑对等差数列内部怎么快速计算。

对于一个项数为 \(m\) ,首项为 \(v\) ,公差为 \(d\) 的等差数列,把转移放到模 \(v\) 意义下做,每次转移就相当与加若干个 \(d\) ,并且考虑转移是若干个互不相交的环,对于一个环可以发现,当前环内部最小的一位肯定不会被更新。所以可以从这一位开始依次更新整个环。

单独考虑一个环,令 \(i,j\) 为展开后环上第 \(i\) 个元素和第 \(j\) 个元素,\(j\) 能更新 \(i\) 当且仅当 \(j < i\) 且 \(i-j < m\) ,这个东西可以直接用单调队列维护。

接下来考虑将两段等差数列的贡献合并,只需要将维护的东西从模一个首项意义转移到另外一个即可,那么只需要对应的位置更新完后,再做一遍长度为之前首项的转移即可,这样子就大概做完了,总复杂度 \(\mathcal O(n\log n)\) 。

code

/*program by mangoyang*/

#include

#define inf ((ll)0x3f3f3f3f3f3f3f3f)

#define Max(a, b) ((a) > (b) ? (a) : (b))

#define Min(a, b) ((a) < (b) ? (a) : (b))

typedef long long ll;

using namespace std;

template

inline void read(T &x){

int ch = 0, f = 0; x = 0;

for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;

for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;

if(f) x = -x;

}

const int N = 500005;

char s[N];

int a[30][N];

ll dp[N], q[N], g[N], w, ans;

int b[N], id[N], vis[N], nxt[N], n, tot;

inline void gao(int *a, int len, int lst){

int v = a[1], d = len > 1 ? a[2] - a[1] : 1;

for(int i = 0; i < v; i++) g[i] = inf, vis[i] = 0;

for(int i = 0; i < lst; i++) g[dp[i]%v] = Min(g[dp[i]%v], dp[i]);

for(int i = 0; i < v; i++) dp[i] = g[i];

for(int i = 0; i < v; i++) if(!vis[i]){

ll mn = dp[i]; int pos = i; vis[i] = 1;

for(int j = (i + lst) % v; j != i; j = (j + lst) % v){

if(dp[j] < mn) mn = dp[j], pos = j;

vis[j] = 1;

}

dp[(pos+lst)%v] = Min(dp[(pos+lst)%v], dp[pos] + lst);

for(int j = (pos + lst) % v; j != pos; j = (j + lst) % v)

dp[(j+lst)%v] = Min(dp[(j+lst)%v], dp[j] + lst);

}

if(len == 1) return;

for(int i = 0; i < v; i++) vis[i] = 0;

for(int i = 0; i < v; i++) if(!vis[i]){

ll mn = dp[i]; int pos = i; vis[i] = 1;

for(int j = (i + d) % v; j != i; j = (j + d) % v){

if(dp[j] < mn) mn = dp[j], pos = j;

vis[j] = 1;

}

int h = 1, t = 1; q[1] = dp[pos], id[1] = 0;

for(int k = 1, j = (pos + d) % v; j != pos; j = (j + d) % v, ++k){

while(h <= t && k - id[h] >= len) ++h;

if(h <= t) dp[j] = Min(dp[j], q[h] + v + (k - id[h]) * d);

while(h <= t && q[t] - id[t] * d > dp[j] - k * d) --t;

q[++t] = dp[j], id[t] = k;

}

}

}

inline void solve(){

read(n), read(w); tot = 0;

scanf("%s", s + 1);

for(int i = 2, j = 0; i <= n; nxt[i++] = j){

while(j && s[j+1] != s[i]) j = nxt[j];

if(s[j+1] == s[i]) j++;

}

for(int x = nxt[n]; x; x = nxt[x]){

int flag = 0;

for(int i = 1; i <= tot; i++)

if(b[i] == 1 || a[i][2] - a[i][1] == (n - x) - a[i][b[i]]){

a[i][++b[i]] = n - x, flag = 1; break;

}

if(!flag) ++tot, a[tot][b[tot]=1] = n - x;

}

dp[0] = n;

for(int i = 1; i < n; i++) dp[i] = inf;

int lst = n;

for(int i = 1; i <= tot; i++)

gao(a[i], b[i], lst), lst = a[i][1];

ans = 0;

for(int i = 0; i < lst; i++) if(dp[i] <= w) ans += (w - dp[i]) / lst + 1;

cout << ans << endl;

}

int main(){

int T; read(T); while(T--) solve();

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值