【WC2016】论战捆竹竿
已经快三周了啊……终于把挖的坑填了……
首先显然是把除了自身的所有border拿出来,即做 \(\left\{ n - b_1, n - b_2, \dots, n - b_k, n \right\}\) 的完全背包。但是值域很大,所以考虑同余最短路。
首先,由 [国家集训队]墨墨的等式 的经验,显然可以 \(O(n^2)\),因为最长border可以很小,所以可以卡到满。
然后就要一个性质:一个串的border可以分成 \(O\left( \log n \right)\) 个等差数列。证明咕咕咕。
有了这个,就可以干一些有趣的事:
考虑一个有趣的暴力,每次更新一个首项为 \(x\),公差为 \(d\) 的等差数列时,可以把点按模 \(d\) 剩余系分类,这样子dp更新的区间是连续的。线段树搞搞,因为没有负权所以可以跑dijkstra,然后同样可以不保存边。最后再像之前那题的经验,每个等差数列分别更新。这样复杂度两个 \(\log\),空间 \(O\left(n\right)\)。(来自官方题解)
然而这样做复杂度还是太大了。能不能不用线段树啊?
我们要将一个等差数列有效边压到 \(O(n)\),那么也就剩余系有希望了。
观察到一个等差数列是 \(\left[x, x + d, x + 2d, \dots\right]\),可以考虑模 \(x\),然后在模 \(x\) 的剩余系下转移,显然还是构成了一堆环。
先考虑如果是在模 \(x\) 意义下的情况的DP转移,注意到等差数列长度的限制,转移点不能太远,因此考虑单调队列维护(也就是滑动窗口类似的)。
然后考虑如何在模 \(P\) 意义下转换为模 \(Q\)。
如果直接将所有能搞出的方案放进去,发现能表示出来的都是 \(dis_i + k \cdot P\) 类型的,这启发我们先把所有 \(dis_i\) 扔到模 \(Q\) 意义下序列上,然后用 \(P\) 更新同余最短路,这样同样能表示出 \(dis_i + k \cdot P\) 了。
所以找出所有等差数列后,对于每个数列分别先转换模数,再做dp转移。注意这道题卡常数(该优化的都优化上去就好了,比如下标加的时候用取模优化)。
我写挂一堆地方:直接拿border长度上去DP,单调队列少个等号,转换模数时用了 \(i\) 而不是 \(dis_i\) 初始化……
#include <bits/stdc++.h>
struct data {
int x, d, c;
data() { x = d = c = 0; }
bool ins(int v) {
if (!c) return x = v, d = 0, c = 1, true;
if (!d) return d = v - x, c = 2, true;
if (x + c * d == v) return ++c, true;
return false;
}
} ;
const int MAXN = 500010;
typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3fLL;
typedef std::vector<data> V;
struct KMP {
int nxt[MAXN];
V build(char * buf, int len) {
for (int i = 1; i <= len; ++i) {
int now = nxt[i - 1];
while (now && buf[now + 1] != buf[i]) now = nxt[now];
nxt[i] = buf[now + 1] == buf[i] && now + 1 != i ? now + 1 : 0;
}
V res; data rt; int now = nxt[len];
while (now) {
if (!rt.ins(now))
res.push_back(rt), (rt = data()).ins(now);
now = nxt[now];
}
if (rt.x) res.push_back(rt);
for (auto & t : res)
t.x = len - t.x, t.d = -t.d;
std::reverse(res.begin(), res.end());
return res;
}
} kmp;
int n; LL W;
LL dis[MAXN];
int mod;
void getmax(LL & x, LL y) { x < y ? x = y : 0; }
void getmin(LL & x, LL y) { x > y ? x = y : 0; }
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void trans(int v) {
static LL d2[MAXN];
memset(d2, 0x3f, v << 3);
for (int i = 0; i < mod; ++i) getmin(d2[dis[i] % v], dis[i]);
const int G = gcd(v, mod);
for (int i = 0; i < G; ++i) {
int at = i;
int tm = mod % v;
for (int j = (i + mod) % v; j != i; j += tm - v, j += j >> 31 & v)
if (d2[j] < d2[at]) at = j;
LL now = d2[at] + mod;
for (int j = (at + mod) % v; j != at; j += tm - v, j += j >> 31 & v)
getmin(d2[j], now), now = std::min(now, d2[j]) + mod;
}
mod = v;
memcpy(dis, d2, mod << 3);
}
struct qnode {
LL v; int at;
qnode() { v = at = 0; }
qnode(LL x, int y) { v = x, at = y; }
} Q[MAXN];
void dp(int v, int D, int C) {
trans(v);
const int G = gcd(v, D);
for (int i = 0; i < G; ++i) {
int at = i;
int tm = D % v;
for (int j = (i + D) % v; j != i; j += tm - v, j += j >> 31 & v)
if (dis[j] < dis[at]) at = j;
int qb = 1, qe = 0;
for (int j = at, k = 0; k * G != v; j += tm - v, j += j >> 31 & v, ++k) {
while (qb <= qe && Q[qb].at + C <= k) ++qb;
if (qb <= qe) {
auto x = Q[qb];
getmin(dis[j], v + x.v + (k - x.at) * D);
}
while (qb <= qe && Q[qe].v + (k - Q[qe].at) * D >= dis[j]) --qe;
Q[++qe] = (qnode) {dis[j], k};
}
}
}
int main() {
std::ios_base::sync_with_stdio(false), std::cin.tie(0);
int T; std::cin >> T;
while (T --> 0) {
static char buf[MAXN];
std::cin >> n >> W >> buf + 1; W -= n;
mod = n; memset(dis, 0x3f, n << 3); *dis = 0;
for (auto t : kmp.build(buf, n)) dp(t.x, t.d ? t.d : 1, t.c);
LL ans = 0;
for (int i = 0; i != mod; ++i)
dis[i] <= W ? ans += (W - dis[i]) / mod + 1 : 0;
std::cout << ans << std::endl;
}
return 0;
}
【WC2016】论战捆竹竿的更多相关文章
- luogu P4156 [WC2016]论战捆竹竿
传送门 官方题解(证明都在这) 神仙题鸭qwq 转化模型,发现这题本质就是一个集合,每次可以加上集合里的数,问可以拼出多少不同的数 首先暴力需要膜意义下的最短路,例题戳这 然后这个暴力可以优化成N^2 ...
- Luogu4156 WC2016 论战捆竹竿 KMP、同余类最短路、背包、单调队列
传送门 豪华升级版同余类最短路-- 官方题解 主要写几个小trick: \(1.O(nm)\)实现同余类最短路: 设某一条边长度为\(x\),那么我们选择一个点,在同余类上不断跳\(x\),可以形成一 ...
- 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」论战捆竹竿
「WC2016」论战捆竹竿 前置知识 参考资料:<论战捆竹竿解题报告-王鉴浩>,<字符串算法选讲-金策>. Border&Period 若前缀 \(pre(s,x)\ ...
- UOJ#172. 【WC2016】论战捆竹竿 字符串 KMP 动态规划 单调队列 背包
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ172.html 题解 首先,这个问题显然是个背包问题. 然后,可以证明:一个字符串的 border 长度可 ...
- UOJ#172. 【WC2016】论战捆竹竿
传送门 首先这个题目显然就是先求出所有的 \(border\),问题转化成一个可行性背包的问题 一个方法就是同余类最短路,裸跑 \(30\) 分,加优化 \(50\) 分 首先有个性质 \(borde ...
- 【LuoguP4156】论战捆竹竿
题目链接 题意简述 你有一个长度为 n 的字符串 , 将它复制任意次 , 复制出的串的前缀可以与之前的串的后缀重叠在一起 , 问最后总共可能的长度数目 , 长度不能超过 \(w\) 多组数据. \(n ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
随机推荐
- 【经典问题】maximum subset sum of vectors
AtCoder Beginner Contest 139 Task F Engines 题目大意 给定 $n$ 个二维向量,从中选出若干个,使得它们的和的模最大. 分析 这是一个经典问题,还有一种提法 ...
- # IDEA相关知识
目录 IDEA相关知识 安装目录下: 配置目录下: 工程目录下: 名词解释 IDEA相关知识 安装目录下: bin:启动文件,配置信息,IDEA的一些属性信息 jre64:IDEA自带的运行环境 li ...
- api返回数据
控制器里调用方法 <?php namespace app\admin\controller; use app\admin\controller\Base; class Index extends ...
- java 给定一个日期期间 返回形如Mar 2015 3/20-3/31的数据
最近一个项目中有个前台对于表头要求: 给定一个日期期间返回形如 Mar 2015 3/20-3/31Apr 2015 4/1-4/30 这样的月年数据,简单的写了下代码,暂时没想到更好的办法 例如传进 ...
- 最小生成树之Prim Kruskal算法(转)
最小生成树 首先,生成树是建立在无向图中的,对于有向图,则没有生成树的概念,所以接下来讨论的图均默认为无向图.对于一个有n个点的图,最少需要n-1条边使得这n个点联通,由这n-1条边组成的子图则称为原 ...
- Git复习(十一)之常见命令用法
创建版本库 git init 进入一个文件,执行该命令此时目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了. ...
- pycharm设置用滑轮改变字体大小
在电脑第一次安装pycharm之后,发现每次调整代码界面的字体,总是需要到setting里面调整,这样非常不方便,特别是对于代码量很多的时候,我们有时候需要把目光聚焦到某一句代码,这个时候就需要放大, ...
- 07 Python爬虫验证码处理
大部分门户网站在进行登录的时候,如果用户连续登录的次数超过3次或者5次的时候,就会在登录页中动态生成验证码.通过验证码达到分流和反爬的效果. 一. 云打码平台处理验证码的流程: 1.对携带验证码的页面 ...
- 今天给大家分享一下js中常用的基础算法
今天给大家分享一下js中常用的基础算法,废话不多说,直接上代码: 1.两个数字调换顺序 ,b= function fun(a,b){ b = b - a ;// a = 2 ; b = 2 a = a ...
- vue-复制功能插件-兼容性最好的插件
记录给自己用,不进同一次坑: https://github.com/Inndy/vue-clipboard2