题目链接

题意描述

给定一个字符串 \(s\),你初始拥有一个空串 \(t\),每次可以选择这个字符串的一个 Border,去掉它后接在 \(t\) 的后面,操作后 \(s\) 不变,给出一个上限 \(w\),求出在 \([1,w]\) 中有多少长度可以被拼出。

题目分析

首先可以看出,抛开字符串,把每个可以拼上去的长度筛选出来,问题就转化为有 \(k\) 个不同的数,通过加法操作可以表示出范围内的多少个数,这显然是一个同余最短路的问题。

模仿通常同余最短路的操作,从每个点延伸 \(k\) 条边后跑一边最短路即可,但是这样只能得到 30 分,原因在于边数过大。

我们考虑优化,既然边这么多,能不能去掉一些没有用的部分,反过来考虑字符串 Border 的一些性质,注意到周期好像是个很有用的部分,能把冗余部分压缩,我们试图从这方面考虑。

对于周期和 Border,我们有一个很显然的性质:每个长度大于 \(\frac{|s|}{2}\) 的 Border 剩下那部分如果长度是 \(|s|\) 的因子,那么它是原字符串的一个周期,而任意一个周期与一个最短周期,记为 \(p\),\(q\)(\(p>q\)),\(\gcd(p,q)\) 也是原字符串的一个周期。

因为 \(p\) 和 \(q\) 的最大公因数也是一个周期,而 \(\gcd(p,q)\leq\min(p,q)\),从上述定义可以看出 \(q=\gcd(p,q)\),因此 \(q|p\),当 \(p\) 所对应的 Border 取最小的时,即 \(p\) 最大,我们就可以发现 \(q\) 复制后只要不超过 \(p\),一定对应一个 Border,而这些 Border 刚好组成一个等差数列

因为所有的周期都能由最小周期组成,所以所有大于 \(\frac{|s|}{2}\) 的 Border 都构成一个等差数列,而把小于的最大的 Border 提出来变成一个新字符串继续递归,可以得到最多不超过 \(\log(n)\) 条等差数列。

这个性质很有用,我们可以把每条数列单独拉出来跑同余最短路,优化边数即可达到目的。

具体实现

把最小的当成模数,每个点就只有一条出边,整个图总共会形成 \(\gcd(a,d)\)个简单环(\(a\) 为 最小值,\(d\),为公差)。

接着把环分离,从最小值开始转圈跑最短路,最后跑下一条等差数列的时候改变一下模数即可(如果从小到大跑只用把每个最小值所在位置,改成模新模数的位置,并查看前面会不会对后面增加的同余类产生影响即可)。

一共有 \(\log(n)\) 条数列,每条数列跑最短路复杂度为 \(O(n)\),整体时间为 $ O(n \log n)$。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#include<cstring>
#include<queue>
#define N 500005
using namespace std;
typedef unsigned long long ll;
ll cnt,bor[N<<3],nex[N<<3];
ll res[N<<3];
ll dis[N<<3],lim;
struct Node{
ll num,val;
};
ll top,Q[N<<3],q2[N<<3];
char s[N];
ll gcd(ll x,ll y){
if(!y) return x;
else return gcd(y,x%y);
}
void get_border(ll n){
ll ans=0;
for(ll i=2;i<=n;i++){
while(ans && s[ans+1]!=s[i]) ans=nex[ans];
if(s[ans+1]==s[i]) ans++;
nex[i]=ans;
} while(ans){bor[++cnt]=n-ans;ans=nex[ans];} bor[++cnt]=n;
}
void rfs(ll len){
ll siz=gcd(lim,len);
for(ll i=0;i<lim;i++)res[i]=dis[i];
for(ll i=0;i<len;i++)dis[i]=0x7f7f7f7f7f7f7f7f;
for(ll i=0;i<lim;i++){
ll tmp=res[i]%len;
dis[tmp]=min(dis[tmp],res[i]);
}
for(ll i=0;i<siz;i++){
top=0;Q[++top]=i;ll tmp=(i+lim)%len;
while(tmp!=i){Q[++top]=tmp;tmp=(tmp+lim)%len;}
for(ll j=top+1;j<=top*2;j++) Q[j]=Q[j-top];
for(ll j=2;j<=(top<<1);j++) dis[Q[j]]=min(dis[Q[j]],dis[Q[j-1]]+lim);
}
lim=len; }
void update(ll a,ll d,ll num){
if(d<0) return;
ll siz=gcd(lim,d);
for(ll i=0;i<siz;i++){
top=0;ll tmp=(i+d)%lim;Q[++top]=i;
while(tmp!=i){Q[++top]=tmp;tmp=(tmp+d)%lim;}
tmp=1;
for(ll j=2;j<=top;j++) if(dis[Q[j]]<dis[Q[tmp]]) tmp=j;
for(ll j=1;j<=top;j++) res[j]=Q[j];
for(ll j=tmp;j<=top;j++) Q[j-tmp+1]=res[j];
for(ll j=1;j<tmp;j++) {Q[j+(top-tmp+1)]=res[j];}
ll head=1,tail=1;q2[1]=1;
for(ll j=2;j<=top;j++){ while(head<=tail && j-q2[head]>num) head++; dis[Q[j]]=min(dis[Q[j]],dis[Q[q2[head]]]+a+d*(j-q2[head])); while(tail>=head && dis[Q[q2[tail]]]+d*(j-q2[head])>dis[Q[j]]) tail--;
q2[++tail]=j; }
}
}
ll solve(ll n,ll w){
ll L=1;
while(L<=cnt){
ll R=L;
while(bor[R+1]-bor[R]==bor[L+1]-bor[L]) R++;
rfs(bor[L]);
if(R>cnt) {L=R;continue;}
update(bor[L],(L==R?0:bor[L+1]-bor[L]),R-L);
L=R;
}
ll ans=0;
for(ll i=0;i<lim;i++){
if(dis[i]>w) continue;
ans+=(w-dis[i])/lim+1;
}
return ans;
}
int main(){
ll t;
cin>>t;
while(t--){
ll n,w;
cin>>n>>w>>(s+1);if(w<n){cout<<0<<endl;continue;}cnt=0;w-=n;
lim=n;dis[0]=0;
get_border(n);
cout<<solve(n,w)<<endl;
for(ll i=0;i<n;i++) dis[i]=0x7f7f7f7f7f7f7f7f;
}
}

P4156 [WC2016]论战捆竹竿 题解的更多相关文章

  1. luogu P4156 [WC2016]论战捆竹竿

    传送门 官方题解(证明都在这) 神仙题鸭qwq 转化模型,发现这题本质就是一个集合,每次可以加上集合里的数,问可以拼出多少不同的数 首先暴力需要膜意义下的最短路,例题戳这 然后这个暴力可以优化成N^2 ...

  2. Luogu4156 WC2016 论战捆竹竿 KMP、同余类最短路、背包、单调队列

    传送门 豪华升级版同余类最短路-- 官方题解 主要写几个小trick: \(1.O(nm)\)实现同余类最短路: 设某一条边长度为\(x\),那么我们选择一个点,在同余类上不断跳\(x\),可以形成一 ...

  3. bzoj4406: [Wc2016]论战捆竹竿&&uoj#172. 【WC2016】论战捆竹竿

    第二次在bzoj跑进前十竟然是因为在UOJ卡常致死 首先这个题其实就是一个无限背包 一般做法是同余最短路,就是bzoj2118: 墨墨的等式可以拿到30分的好成绩 背包是个卷积就分治FFT优化那么下面 ...

  4. BZOJ4406 WC2016 论战捆竹竿

    Problem BZOJ Solution 显然是一个同余系最短路问题,转移方案就是所有|S|-border的长度,有 \(O(n)\) 种,暴力跑dijkstra的复杂度为 \(O(n^2\log ...

  5. 「WC2016」论战捆竹竿

    「WC2016」论战捆竹竿 前置知识 参考资料:<论战捆竹竿解题报告-王鉴浩>,<字符串算法选讲-金策>. Border&Period 若前缀 \(pre(s,x)​\ ...

  6. UOJ#172. 【WC2016】论战捆竹竿 字符串 KMP 动态规划 单调队列 背包

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ172.html 题解 首先,这个问题显然是个背包问题. 然后,可以证明:一个字符串的 border 长度可 ...

  7. 【WC2016】论战捆竹竿

    已经快三周了啊--终于把挖的坑填了-- 首先显然是把除了自身的所有border拿出来,即做 \(\left\{ n - b_1, n - b_2, \dots, n - b_k, n \right\} ...

  8. UOJ#172. 【WC2016】论战捆竹竿

    传送门 首先这个题目显然就是先求出所有的 \(border\),问题转化成一个可行性背包的问题 一个方法就是同余类最短路,裸跑 \(30\) 分,加优化 \(50\) 分 首先有个性质 \(borde ...

  9. 【LuoguP4156】论战捆竹竿

    题目链接 题意简述 你有一个长度为 n 的字符串 , 将它复制任意次 , 复制出的串的前缀可以与之前的串的后缀重叠在一起 , 问最后总共可能的长度数目 , 长度不能超过 \(w\) 多组数据. \(n ...

  10. Loj 507 接竹竿 题解

    Loj链接:接竹竿 $ {\scr \color {SkyBlue}{\text{Solution}}} $ 题目大意: 给定一个数组,每次加入一种颜色的数,可以取走与它颜色相同的两个数之间的所有数, ...

随机推荐

  1. 巧用 nc 命令传输文件

    今天在业务上云的时候,遇到了些问题.最终发现问题的根源不好排查,于是-- 把生产环境的全量配置文件,还有日志全量打包下载到开发机器分析! 刚入职不是很久的整个运维团队,也不是很熟悉生产环境(有时候觉得 ...

  2. 大白话讲讲 Go 语言的 sync.Map(一)

    阅读本文大约需要 4.25 分钟. 程序是枯燥乏味的. 在讲 sync.Map 之前,我们先说说什么是 map(映射). 我们每个人都有身份证号码,如果我需要从身份证号码查到对应的姓名,用 map 存 ...

  3. Kubernetes亲和性学习笔记

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是欣宸在学习Kubernetes调度器的 ...

  4. [爬虫]3.4.1 Scrapy框架的基本使用

    Scrapy是一款强大的Python网络爬虫框架,它可以帮助你快速.简洁地编写爬虫程序,处理数据抓取.处理和存储等复杂问题. 1. 安装Scrapy 在开始使用Scrapy之前,你需要先将其安装在你的 ...

  5. 数据库是要拿来用的,不是用来PK先进性的

    周五参加了WAIC后又和一家上海本地的数据库厂商交流了一下午.等我要买高铁票回南京的时候已经买不到票了.好不容易刷到一张到苏州北的高铁票,我就上了车.上车后突然想起还不如就回苏州老家住一晚算了.到家后 ...

  6. python下的jstack - pystack

    背景 python 多进程任务,卡在某个地方没有继续执行也没有报出异常,进程被hang住 日志没有捕获到相关信息,需要知道进程阻塞在哪里,为什么阻塞 jvm提供了jstack.jmap类工具进行性能分 ...

  7. 从 Pulsar Client 的原理到它的监控面板

    背景 前段时间业务团队偶尔会碰到一些 Pulsar 使用的问题,比如消息阻塞不消费了.生产者消息发送缓慢等各种问题. 虽然我们有个监控页面可以根据 topic 维度查看他的发送状态,比如速率.流量.消 ...

  8. Log4j2的Maven依赖及其配置文件

    Maven依赖 <!-- log4j 2依赖--> <dependency> <groupId>org.apache.logging.log4j</group ...

  9. Redis从入门到放弃(9):集群模式

    前面文章我们介绍了Redis的主从模式是一种在Redis中实现高可用性的方式,但也存在一些缺点. 1.主从模式缺点 写入单点故障:在主从模式中,写入操作只能在主节点进行,如果主节点宕机,写入将无法执行 ...

  10. U268603 I Hate This Tree 题解

    传送门 一道纯粹的码力 + 卡常题. 前置 矩阵乘法,线段树. 分析 线段树存矩阵. 构造迭代矩阵: \[\begin{pmatrix}f_i&f_{i-1}\end{pmatrix}\tim ...