「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<bits/stdc++.h>
#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 <class T>
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;
}

原文地址:https://www.cnblogs.com/mangoyang/p/10467065.html

时间: 2024-10-30 02:24:10

「WC2016」论战捆竹竿的相关文章

uoj #172. 【WC2016】论战捆竹竿

#172. [WC2016]论战捆竹竿 这是一个美好的下午,小 W 和小 C 在竹林里切磋捆竹竿的技艺. 竹林里有无数根完全一样的短竹子,每一根竹子由 nn 节组成. 这些竹子比较特别,每一节都被染上了颜色.可能的颜色一共 2626 种,分别用小写英文字母 a 到 z 表示.也就是说,如果把竹子的底端到顶端的颜色按顺序写出来可以排成一个由小写英文字母组成的字符串. 小 W 和小 C 都是捆竹竿的高手,他们知道怎样才能把零散的短竹子捆成一整根长竹竿.初始时你拿着一根短竹子作为当前的竹竿.每次你可以

luogu P4156 [WC2016]论战捆竹竿

传送门 官方题解(证明都在这) 神仙题鸭qwq 转化模型,发现这题本质就是一个集合,每次可以加上集合里的数,问可以拼出多少不同的数 首先暴力需要膜意义下的最短路,例题戳这 然后这个暴力可以优化成N^2的.具体操作是枚举每个数,然后从某个点只用这个数往后跳,这样在膜m意义下可以形成\(gcd(a,m)\)个环.每个环找到dis最小的点,从这个点开始依次遍历整个环,更新后一个位置 有个结论是集合中的数可以分成\(logn\)个等差数列,所以可以每个等差数列贡献答案 然后对于每个等差数列,先把膜m意义

AC日记——「HNOI2017」单旋 LiBreOJ 2018

#2018. 「HNOI2017」单旋 思路: set+线段树: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 100005 #define maxtree maxn<<2 int val[maxtree],tag[maxtree],L[maxtree],R[maxtree],mid[maxtree]; int op[maxn],ki[maxn],bi[maxn],cnt,size,n,ch[maxn]

「随笔」基于当下的思考

马德,说好的技术blog,变成日记本了... 下午的时候莫名其妙的感到很颓废,因为自己的不够强大感到忧虑和危机感十足.现在每每行走在技术的道路上,常觉得如履薄冰,如芒在背. 上大学之前和现在的心态其实差别挺大的,视野的开阔远远不止局限于自己的脚下.不过,这里的「上大学之前」只是一个时间描述词,并不觉得大学是最适合学习的地方,我很失望. 世界上的人无论性别,区域,宗教,兴趣爱好,总可以在互联网上找到志趣相同的人,总是可以不断打破自己的常识与惯性思维.总是有在相同领域比自己更强的人,挺好的. 关于知

「Unity」与iOS、Android平台的整合:3、导出的Android-Studio工程

本文属于「Unity与iOS.Android平台的整合」系列文章之一,转载请注明出处. Unity默认导出的是Android-Eclipse工程,毕竟Eclipse for Android开发在近一两年才开始没落,用户量还是非常巨大的. 个人认为AndroidStudio非常好用,能轻易解决很多Eclipse解决不了或者很难解决的问题. 所以我将Unity导出的Andoid工程分为Eclipse和AndroidStudio两部分. 不过我之后的相关内容都会使用AndroidStudio,希望依然

大数据和「数据挖掘」是何关系?---来自知乎

知乎用户,互联网 244 人赞同 在我读数据挖掘方向研究生的时候:如果要描述数据量非常大,我们用Massive Data(海量数据)如果要描述数据非常多样,我们用Heterogeneous Data(异构数据)如果要描述数据既多样,又量大,我们用Massive Heterogeneous Data(海量异构数据)--如果要申请基金忽悠一笔钱,我们用Big Data(大数据) 编辑于 2014-02-2817 条评论感谢 收藏没有帮助举报作者保留权利 刘知远,NLPer 4 人赞同 我觉得 大数据

开放的智力8:实用「成功学」

可实现的「成功学」 现在我想为这里的年轻人介绍一种可实现的「成功学」.希望这个我自创的理论,可以改变很多人的一生. 当我们评价一个事情值不值得去做.应该花多少精力去做的时候,应该抛弃单一的视角,而是分两个不同的维度来看,一是该事件将给我带来的收益大小(认知.情感.物质.身体方面的收益皆可计入),即「收益值」:二是该收益随时间衰减的速度,我称为「收益半衰期」,半衰期长的事件,对我们的影响会持续得较久较长. 这两个维度正交以后就形成了一个四象限图.我们生活.学习和工作中的所有事情都可以放进这个图里面

Linux 小知识翻译 - 「syslog」

这次聊聊「syslog」. 上次聊了「日志」(lgo).这次说起syslog,一看到log(日志)就明白是怎么回事了.syslog是获取系统日志的工具. 很多UINIX系的OS都采用了这个程序,它承担了「获取系统全部的日志」这个维持系统正常运行的重要任务. syslog的本体是「syslogd」这个daemon(一般翻译成守护进程),常驻内存中获取日志. syslog的特点是可以通过配置文件「/etc/syslog.conf」,对「哪种应用程序?哪种重要度的信息?记录在哪个文件中?」等进行细致的

Linux 小知识翻译 - 「日志」(log)

这次聊聊「日志」. 「日志」主要指系统或者软件留下的「记录」.出自表示「航海日志」的「logbook」. 经常听说「出现问题的时候,或者程序没有安装自己预期的来运行的时候,请看看日志!」. 确实,记录了系统和软件详细运行情况的「日志」是信息的宝库,通过日志来解决问题的事例也非常多. 但事实上,「无论如何也不会看日志」的用户也有很多.理由很简单,日志的信息量非常大,全部用眼睛来看的话是非常吃力的. 而且,英语写的日志也会让英文不好的人敬而远之. 虽说「要养成用眼睛来看日志的习惯」,但实行起来却非常