【LuoguP4156】论战捆竹竿
题意简述
你有一个长度为 n 的字符串 , 将它复制任意次 , 复制出的串的前缀可以与之前的串的后缀重叠在一起 , 问最后总共可能的长度数目 , 长度不能超过 \(w\)
多组数据。
\(n\leq 5*10^5 ,w\leq 10^{18}\)
Sol
显然每次可以重叠的部分是原串的一个 boder
假设这个boder长度为 \(L\) , 那么长度可以只增加 \(n-L\)
那么就是一个存在性完全背包问题。
那么先求出所有boder , 之后显然可以用同余类最短路来求答案。
不过这个样子复杂度太高了。
最短路的做法是有所多余的 , 每一个点只会向后连边 , 连边的模式也是一定的 , 最短路和转移之间的顺序就是没有关系的 , 我们可以分开考虑每一种连边方式 , 假设这个长度为 \(L\) 。
由数学知识我们可以知道他将原来的长度为 \(n\) 的数组分成了 \(gcd(n,L)\) 个在这次转移中互不影响的环 , 我们在每一个环里找到 距离最小的那个点开始转移就可以了。
这样复杂度就是 \(O(T*n^2)\) , 只有 30'
接下来就要用到一个很强的性质了 , 一个字符串的 \(boder\) 形成的等差数列的个数不会超过 \(log\) 个。
证明不会。
这又什么用呢?
考虑一个等差数列在转移的时候有什么优秀的性质。
我们假设首项为\(x\),公差为 \(d\) ,长度为 \(l\)
那么假设我们在模 \(x\) 的意义下进行这些转移 , 这些长度都可以等价于一个长度为 \(d\) 的转移方式!这样我们要考虑的转移方式总数就下降到了 \(log\) 级别。由于长度只有 \(l\) ,我们转移的时候最多就只能从前 \(l-1\) 个转移过来 , 用一个单调队列来维护转移即可。
但是这样还有一个问题 , 因为这样同余类最短路的模数不就一直在变?
其实是可以转换的 , 具体方式为:
假设新的模数 \(p\) 下的dp数组为 \(g\) , 原来的模数为 \(q\) ,数组为 \(f\) ,那么首先可以 \(g[f[i]\%p]=f[i]\) , 这样有什么问题呢?
容易发现 , 由于之前我们相当于是没有进行 长度为 \(q\) 的转移的 , 所以这里的 dp 数组会有些值是不对的 , 这样的话我们重新进行一次长度为 \(q\) 的转移就好了。
code:
#include<bits/stdc++.h>
using namespace std;
#define next NEXTT
#define Set(a,b) memset(a,b,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(a))
const int N=5e5+10;
typedef long long ll;
char S[N];
int next[N];
int T;
ll n,w,ans,f[N],g[N];
int len[N],tot=0,Idex,vis[N];
int head,tail;
int que[N],V[N];
inline void Update(int pL,int nL,int d,int cnt){
Set(g,-1);
for(int i=0;i<pL;++i) if(~f[i]) {int res=f[i]%nL;if(~g[res]) g[res]=min(g[res],f[i]);else g[res]=f[i];}
Copy(f,g);
++Idex;
for(int i=0;i<nL;++i) {
if(vis[i]==Idex) continue;
int mip=i;vis[i]=Idex;
for(int j=(i+pL)%nL;j^i;j=(j+pL)%nL) {
if((!~f[mip])||((~f[j])&&f[mip]>f[j])) mip=j;
vis[j]=Idex;
}
if(~f[mip]){
for(int pre=mip,j=(mip+pL)%nL;j^mip;pre=j,j=(j+pL)%nL){if(!~f[j]) f[j]=f[pre]+pL;else f[j]=min(f[j],f[pre]+pL);}
}
}++Idex;
for(int i=0;i<nL;++i) {
if(vis[i]==Idex) continue;
int mip=i;vis[i]=Idex;
for(int j=(i+d)%nL;j^i;j=(j+d)%nL){
if((!~f[mip])||((~f[j])&&f[j]<f[mip])) mip=j;
vis[j]=Idex;
}
if(!~f[mip]) continue;
head=1,tail=1;que[1]=1;V[1]=mip;
for(int k=2,j=(mip+d)%nL;j^mip;j=(j+d)%nL,++k){
while(head<=tail&&k-que[head]>=cnt) ++head;
if(head<=tail) {if(~f[j]) f[j]=min(f[j],f[V[head]]+(k-que[head])*d+nL);else f[j]=f[V[head]]+(ll)(k-que[head])*d+nL;}
if(~f[j]){
while(head<=tail&&f[j]<=(ll)d*(k-que[tail])+f[V[tail]]) --tail;
que[++tail]=k,V[tail]=j;
}
}
}
return;
}
void Solve(){
int lst=n;--tot;
reverse(len+1,len+1+tot);
int L;
for(int i=1,j;i<=tot;i=j) {
j=i+1;int d=len[i]-len[j];
if(j>tot) d=0;
while(j<=tot&&len[j-1]-len[j]==d) ++j;
L=len[j-1];Update(lst,L,d,j-i);lst=L;
}
ans=0;
for(int j=0;j<lst;++j) if(~f[j]&&f[j]<=w) ans+=(w-f[j])/lst+1;
return;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("jie.in","r",stdin);
freopen("jie.out","w",stdout);
#endif
scanf("%d",&T);
while(T--){
Set(vis,0);Idex=0;
scanf("%lld %lld",&n,&w);
ans=0;w-=n;scanf("%s",S+1);
register int i,j=0;Set(next,0);
for(i=2;i<=n;++i) {
while(j&&S[i]!=S[j+1]) j=next[j];
if(S[i]==S[j+1]) ++j;next[i]=j;
}j=n;tot=0;
while(j){len[++tot]=n-next[j];j=next[j];}
Set(f,-1),f[0]=0,Solve();
printf("%lld\n",ans);
}
}
【LuoguP4156】论战捆竹竿的更多相关文章
- 「WC2016」论战捆竹竿
「WC2016」论战捆竹竿 前置知识 参考资料:<论战捆竹竿解题报告-王鉴浩>,<字符串算法选讲-金策>. Border&Period 若前缀 \(pre(s,x)\ ...
- UOJ#172. 【WC2016】论战捆竹竿 字符串 KMP 动态规划 单调队列 背包
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ172.html 题解 首先,这个问题显然是个背包问题. 然后,可以证明:一个字符串的 border 长度可 ...
- luogu P4156 [WC2016]论战捆竹竿
传送门 官方题解(证明都在这) 神仙题鸭qwq 转化模型,发现这题本质就是一个集合,每次可以加上集合里的数,问可以拼出多少不同的数 首先暴力需要膜意义下的最短路,例题戳这 然后这个暴力可以优化成N^2 ...
- Luogu4156 WC2016 论战捆竹竿 KMP、同余类最短路、背包、单调队列
传送门 豪华升级版同余类最短路-- 官方题解 主要写几个小trick: \(1.O(nm)\)实现同余类最短路: 设某一条边长度为\(x\),那么我们选择一个点,在同余类上不断跳\(x\),可以形成一 ...
- UOJ#172. 【WC2016】论战捆竹竿
传送门 首先这个题目显然就是先求出所有的 \(border\),问题转化成一个可行性背包的问题 一个方法就是同余类最短路,裸跑 \(30\) 分,加优化 \(50\) 分 首先有个性质 \(borde ...
- bzoj4406: [Wc2016]论战捆竹竿&&uoj#172. 【WC2016】论战捆竹竿
第二次在bzoj跑进前十竟然是因为在UOJ卡常致死 首先这个题其实就是一个无限背包 一般做法是同余最短路,就是bzoj2118: 墨墨的等式可以拿到30分的好成绩 背包是个卷积就分治FFT优化那么下面 ...
- BZOJ4406 WC2016 论战捆竹竿
Problem BZOJ Solution 显然是一个同余系最短路问题,转移方案就是所有|S|-border的长度,有 \(O(n)\) 种,暴力跑dijkstra的复杂度为 \(O(n^2\log ...
- 【WC2016】论战捆竹竿
已经快三周了啊--终于把挖的坑填了-- 首先显然是把除了自身的所有border拿出来,即做 \(\left\{ n - b_1, n - b_2, \dots, n - b_k, n \right\} ...
- WC2016自测
挑战NPC 原题链接 爆搜20分,贪心10分,网络流30分 //挑战NPC #include <cstdio> #include <cstring> #include < ...
随机推荐
- docker镜像和加速
首先,需要明确一个问题:Mirror 与 Private Registry 有什么区别? Private Registry 是开发者或者企业自建的镜像存储库,通常用来保存企业内部的 Docker 镜像 ...
- MVC、MVP 和 MVVM
MVC Model–View–Controller 模型:管理应用程序的数据.逻辑和规则 视图:展示数据(可以直接从模型中获取数据) 控制器:接收输入并将其转化成模型和视图的命令 MVP Model– ...
- 测开之路一百三十九:会话管理之cookie写入、读取、和更新
机制:服务器端发送的小段文本信息存储在客户端硬盘 功能:记录用户偏好,请求.页面.站点间共享信息 特点:易丢失.安全隐患 添加cookie,需要用到make_respons.set_cookie @a ...
- lgb模板
一 回归 1 提取训练集和测试集 2 制作标签,并检查标签是否有异常值 2 划分数据 https://www.jb51.net/article/152574.htm 3 建立model,写评价函数 h ...
- 【MM系列】SAP 在SAP中更改基本计量单位
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP 在SAP中更改基本计量单位 ...
- 6.文件所有权和权限----免费设置匿名----Windows键盘记录器----简介和python模块
文件所有权和权限 touch --help cd Desktop mkdir Folder cd Folder clear touch Test1 Test2 Test3 Test4 ls ls -l ...
- 【Linux开发】CCS远程调试ARM,AM4378
注意一点:CCS也是安装在Linux主机上的,不是安装在Windows上的,我在Windows上做出了很多尝试,但最终也不没明白究竟要用怎样的格式去执行在ARM-Linux应用程序,out文件ELF可 ...
- 【Linux开发】Linux启动脚本设置
前言linux有自己一套完整的启动 体系,抓住了linux启动 的脉络,linux的启动 过程将不再神秘.阅读之前建议先看一下附图.本文中假设inittab中设置的init tree为:/etc/rc ...
- Go语言入门篇-命令 与 语法
一.命令基础 1. go run : 用于运行命令源码文件(如:go run helloworld.go) 只能接受一个命令源码文件以及若干个库源码文件作为文件参数 其内部操作步骤: (1)先编译源码 ...
- 【Python基础】_1 Python简介
1 Python简介 Python是一种计算机程序设计语言.是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越来越多被用于独立的.大型项 ...