【洛谷5398】[Ynoi2018]GOSICK(二次离线莫队)
题目:
当我刚学莫队的时候,他们告诉我莫队能解决几乎所有区间问题;
现在,当我发现一个区间问题似乎难以用我所了解的莫队解决的时候,他们就把这题的正解叫做 XXX 莫队。——题记
(以上皆为瞎扯,纯属虚构,请勿当真)
分析:
先转化一下题目:如果允许每次询问都暴力把区间扫一遍,那么每扫到一个数 \(i\) ,就统计已经扫过的部分中有多少个 \(j\) 满足 \(a_j\) 是 \(a_i\) 的因数(即取数对 \((i,j)\) )或倍数 (即取数对 \((j,i)\) )。注意,因为 \((i,j)\) 和 \((j,i)\) 是不同的,如果 \(a_i=a_j\) ,那么答案要加 \(2\) 。
考虑莫队。每次往当前区间加入或删除一个元素的时候,需要统计当前区间有多少个元素是该元素的因数或倍数(为了和题目中的「询问」区分,我们不妨把这个操作简称为一个数对一个区间「统计」),然后给答案加上或减去这个数量。这样单次修改的复杂度是 \(O(n)\) ,总时间复杂度高达 \(O(n^2\sqrt{n})\) 。 GG
黑科技来了 :由于每次当前区间两端点的移动是已知的,所以我们把两端点移动时出现的「统计」离线下来处理(是谓「二次离线」)。
如何离线呢?我们发现每次都是一个数 \(a\) 对一个区间 \([l,r]\) 统计,而这个操作又可以拆分为对 \([1,l)\) 和 \([1,r]\) 两个前缀统计。
然后开始大力分类讨论 ……
第一,当左端点从 \(l\) 左移到 \(l'\) 时,是每个 \(i\in[l',l)\) 对 \([i,r]\) 统计,即对 \([1,i)\) 和 \([1,r]\) 统计;
第二,当左端点从 \(l\) 右移到 \(l'\) 时,是每个 \(i\in[l,l')\) 对 \([i,r]\) 统计,即对 \([1,i)\) 和 \([1,r]\) 统计;
第三,当右端点从 \(r\) 左移到 \(r'\) 时,是每个 \(i\in(r',r]\) 对 \([l,i]\) 统计,即对 \([1,l)\) 和 \([1,i]\) 统计;
第四,当右端点从 \(r\) 右移到 \(r'\) 时,是每个 \(i\in(r,r']\) 对 \([l,i]\) 统计,即对 \([1,l)\) 和 \([1,i]\) 统计。
可以看出统计分为两大类。第一类是 \(i\) 对于 \([1,i)\) (或 \([1,i]\) ,但很明显这两个差距很小)统计,第二类是一个区间中的 \(i\) 对一个固定的前缀分别统计(如第一种情况中是所有 \(i\in[l',l)\) 对固定的前缀 \([1,r]\) 统计)。
第一类可以预处理。第二类可以按前缀从小到大排序,暴力扫当前前缀对应的所有统计(根据莫队的复杂度证明,端点移动距离之和是 \(O(n\sqrt{n})\) )。
事实上这两类统计可以抽象成同一个问题:维护一个结构,支持「插入一个数」和「给定 \(k\) ,查询已经插入的数中有多少个是 \(k\) 的因数或倍数」。插入次数是 \(O(n)\) ,查询次数是 \(O(n\sqrt{n})\) 。
既然查询次数远比插入次数多,自然考虑维护每个数的答案。插入一个数时,因为可以在 \(O(\sqrt{n})\) 时间内遍历一个数的所有因数,所以直接暴力修改所有因数的答案即可。同理,当 \(k\) 大于一个阈值,比如 \(S\) ,倍数也可以暴力修改,单次时间复杂度不超过 \(O(\frac{n}{S})\) ,据说此处选择 \(S=32\) 。
那么当 \(k\leq S\) 怎么办呢?这里有一个很巧妙的做法。预处理出每个数能否被 \([1,S]\) 中的某个数整除(这个结果下称「状态」),并将结果压成一个 \(S\) 位的二进制数,并开一个大小为 \(2^S\) 的桶记录「这个状态的数的答案应该统一加上多少」。当插入 \(k(k\leq S)\) 时,暴力将所有 \(k\) 这一位上是 \(1\) 的状态的答案加 \(1\) 。查询时不光查询前一段话中维护的答案,也要加上这一段话中对应状态的答案。由于 \(2^{32}\) 太大了,因此把因数分成 \(8\) 个一组,每个数对应 \(4\) 个状态。这样插入时只需要修改 \(2^{8-1}\) 个状态,查询时需要查询这个数对应的 \(4\) 个状态。
还有一个小小的细节。由于默认任意两个相同的数会给答案贡献 \(2\) (原因在文首说了),如果 \(i\) 对 \([l,r]\) 统计,而 \(i\in[l,r]\) ,那么 \((i,i)\) 这个数对就被算了两次。没关系,我们只需要放任它算两次,最后给所有询问的答案减去区间长度即可。也由于这个原因,\(i\) 对 \([1,i]\) 统计的答案应该是 \(i\) 对 \([1,i)\) 统计的答案加 \(2\) 。
代码:
注意每次算出的是答案相对于上次的变化量,所以最后要按照处理询问的顺序做一遍前缀和。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <vector>
#include <cmath>
using namespace std;
namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int N = 1e5 + 10, M = 1e5, Q = N, SS = 8, S = 32;
const bool ADD = true, SUB = false;
int n, m, block, blnum, belong[N], arr[N], fac[N][4], facnum[4][1 << SS], now[N], f[N];
ll ans[Q];
struct _ask
{
int l, r, id;
bool operator < (const _ask &b) const
{
return belong[l] == belong[b.l] ? r < b.r : belong[l] < belong[b.l];
}
}ask[Q];
struct node
{
int l, r, id;
bool type;
node(const int _l, const int _r, const int _id, const bool _t)
: l(_l), r(_r), id(_id), type(_t) {}
};
vector<node> v[N];
void insert(const int a)
{
for (int i = 1; i * i <= a; i++)
if (a % i == 0)
{
++now[i];
if (i * i < a)
++now[a / i];
}
if (a > S)
{
for (int i = a; i <= M; i += a)
if (i > S)
++now[i];
}
else
{
int bl = (a - 1) / SS, pos = (a - 1) % SS;
for (int i = 0; i < (1 << SS); i++)
if (i & (1 << pos))
++facnum[bl][i];
}
}
int query(const int a)
{
int ans = now[a];
for (int i = 0; i < 4; i++)
ans += facnum[i][fac[a][i]];
return ans;
}
int work()
{
read(n), read(m);
block = sqrt(n), blnum = ceil(double(n) / double(block));
for (int i = 1; i <= M; i++)
for (int j = 0; j < 4; j++)
for (int k = j * SS + 1; k <= (j + 1) * SS; k++)
if (i % k == 0)
fac[i][j] |= (1 << (k - j * SS - 1));
for (int i = 1; i <= n; i++)
read(arr[i]), belong[i] = (i - 1) / block + 1;
for (int i = 1; i <= m; i++)
read(ask[i].l), read(ask[i].r), ask[i].id = i;
sort(ask + 1, ask + m + 1);
for (int i = 1; i <= n; i++)
f[i] = query(arr[i]), insert(arr[i]);
for (int i = 1, l = 1, r = 1; i <= m; i++)
{
if (ask[i].l < l)
{
v[r].push_back(node(ask[i].l, l - 1, ask[i].id, ADD));
while (ask[i].l < l)
ans[ask[i].id] -= f[--l];
}
if (ask[i].l > l)
{
v[r].push_back(node(l, ask[i].l - 1, ask[i].id, SUB));
while (ask[i].l > l)
ans[ask[i].id] += f[l++];
}
if (ask[i].r > r)
{
v[l - 1].push_back(node(r + 1, ask[i].r, ask[i].id, SUB));
while (ask[i].r > r)
ans[ask[i].id] += f[++r] + 2;
}
if (ask[i].r < r)
{
v[l - 1].push_back(node(ask[i].r + 1, r, ask[i].id, ADD));
while (ask[i].r < r)
ans[ask[i].id] -= f[r--] + 2;
}
}
memset(facnum, 0, sizeof(facnum));
memset(now, 0, sizeof(now));
for (int i = 1; i <= n; i++)
{
insert(arr[i]);
for (vector<node>::iterator it = v[i].begin(); it != v[i].end(); it++)
for (int j = it->l; j <= it->r; j++)
ans[it->id] += query(arr[j]) * (it->type == ADD ? 1 : -1);
}
ans[0] = 2;
for (int i = 1; i <= m; i++)
ans[ask[i].id] += ans[ask[i - 1].id];
for (int i = 1; i <= m; i++)
ans[ask[i].id] -= ask[i].r - ask[i].l + 1;
for (int i = 1; i <= m; i++)
write(ans[i]), putchar('\n');
return 0;
}
}
int main()
{
#ifdef BlueSpirit
freopen("5398.in", "r", stdin);
#endif
return zyt::work();
}
【洛谷5398】[Ynoi2018]GOSICK(二次离线莫队)的更多相关文章
- 洛谷P5398 [Ynoi2018]GOSICK(二次离线莫队)
题面 传送门 题解 维包一生推 首先请确保您会二次离线莫队 那么我们现在的问题就是怎么转移了,对于\(i\)和前缀\([1,r]\)的贡献,我们拆成\(b_i\)和\(c_i\)两部分,其中\(b_i ...
- [Ynoi2019模拟赛]Yuno loves sqrt technology II(二次离线莫队)
二次离线莫队. 终于懂了 \(lxl\) 大爷发明的二次离线莫队,\(\%\%\%lxl\) 二次离线莫队,顾名思义就是将莫队离线两次.那怎么离线两次呢? 每当我们将 \([l,r]\) 移动右端点到 ...
- 【洛谷4396/BZOJ3236】[AHOI2013]作业(莫队+分块/树状数组/线段树)
题目: 洛谷4396 BZOJ3236(权限) 这题似乎BZOJ上数据强一些? 分析: 这题真的是--一言难尽 发现题面里没说权值的范围,怕出锅就写了离散化.后来经过面向数据编程(以及膜神犇代码)知道 ...
- Bzoj2038/洛谷P1494 小Z的袜子(莫队)
题面 Bzoj 洛谷 题解 考虑莫队算法,首先对询问进行分块(分块大小为\(sqrt(n)\)),对于同一个块内的询问,按照左端点为第一关键字,右端点为第二关键字排序.我们统计这个区间内相同的颜色有多 ...
- luogu P4887 模板 莫队二次离线 莫队 离线
LINK:模板莫队二次离线 很早以前学的知识点 不过 很久了忘了. 考虑暴力 :每次莫队更新的时候 尝试更新一个点到一个区间的答案 可以枚举二进制下位数为k的数字 看一下区间内的这种数字有多少个. 不 ...
- 洛谷P4887 第十四分块(前体)(二次离线莫队)
题面 传送门 题解 lxl大毒瘤 我们考虑莫队,在移动端点的时候相当于我们需要快速计算一个区间内和当前数字异或和中\(1\)的个数为\(k\)的数有几个,而这个显然是可以差分的,也就是\([l,r]\ ...
- 洛谷 P1972 [SDOI2009]HH的项链【莫队算法学习】
P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...
- 洛谷 P3674 小清新人渣的本愿 [莫队 bitset]
传送门 题意: 给你一个序列a,长度为n,有Q次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ...
- B 洛谷 P3604 美好的每一天 [莫队算法]
题目背景 时间限制3s,空间限制162MB 素晴らしき日々 我们的情人,不过是随便借个名字,用幻想吹出来的肥皂泡,把信拿去吧,你可以使假戏成真.我本来是无病呻吟,漫无目的的吐露爱情---现在这些漂泊不 ...
随机推荐
- httpclient失败重连机制
HttpClient 底层会默认超时自动重发3次,DefaultHttpRequestRetryHandler源码 /** * Create the request retry handler ...
- 使用nginx代理weblogic负载方案
之前一直用apache来做weblogic的前端,由于nginx对静态内容的出色性能,不得不转投nginx.这里就不 再写weblogic的安装了. 安装nginx nginx需要pcre做支持,一般 ...
- Office EXCEL 如何将复制的一堆数据按空格断开
1 复制粘贴一堆数据,点击数据-分类,然后点击下一步 2 一直下一步 3 最后效果如下图所示
- payload和formData有什么不同?
最近做项目的时候,在通过post请求向服务端发送数据的时候,请求失败了.错误信息如下: 返回的400(bad request)错误,说明客户端这边发送的请求是有问题的. 和通过jquery中的ajax ...
- 编译iOS使用的.a库文件
首先是须要编译成.a的源文件 hello.h: #ifndef __INCLUDE_HELLO_H__ #define __INCLUDE_HELLO_H__ void hello(const cha ...
- The data property "dialogVisble" is already declared as a prop. Use prop default value instead报错原因
vue中使用props传递数据就不能在子组件的data中用同样的名字(比如dialogVisble)了,否则会报错.解决方法直接去掉data中的相同名字改为其他的.
- 使用mongostat监视mongodb
1, 监视一个mongod mongostat 10.80.1.1:27018 1,监视replica set mongostat --host rs0/10.80.1.1:27018,10.80.1 ...
- 【转载】在VS2008中使用WSE 3.0过程全记录
WSE全称是Web Service Enhancement,提供了更好的安全性实现,以及大对象传输的设计. 有关WSE的一些介绍,如果不清楚,可以参考下面的链接 官方介绍:http://www.mic ...
- Effective JavaScript Item 39 绝不要重用父类型中的属性名
本系列作为Effective JavaScript的读书笔记. 假设须要向Item 38中的Actor对象加入一个ID信息: function Actor(scene, x, y) { this.sc ...
- MySQL 高可用架构在业务层面细化分析研究
相对于传统行业的相对服务时间9x9x6或者9x12x5,由于互联网电子商务以及互联网游戏的实时性,所以服务要求7*24小时,业务架构无论是应用还是数据库,都须要容灾互备.在mysql的体系中,最好通过 ...