P5398 [Ynoi2018] GOSICK

给你一个序列 a a a,每次询问给一个区间 [ l , r ] [l,r] [l,r]

查询 l ≤ i , j ≤ r l \leq i,j \leq r li,jr a i a_i ai a j a_j aj 倍数的二元组 ( i , j ) (i,j) (i,j) 的个数。

1 ≤ n , m , a i ≤ 5 × 1 0 5 1 \leq n,m,a_i\leq 5\times 10^5 1n,m,ai5×105,时限 3 s 3\text{s} 3s,空限 128 MB 128\text{MB} 128MB

sol

「点缀光辉的第十四分块」。

考虑二次离线莫队。

关于二次离线莫队,不懂点

a i a_i ai 在区间 [ l , r ] [l,r] [l,r] 的因数个数为 f 1 ( i , [ l , r ] ) f_1(i,[l,r]) f1(i,[l,r]),倍数个数为 f 2 ( i , [ l , r ] ) f_2(i,[l,r]) f2(i,[l,r])

则右端点向右移动加入一个数的贡献为 f 1 ( r , [ l , r ] ) + f 2 ( r , [ l , r ] ) f_1(r,[l,r])+f_2(r,[l,r]) f1(r,[l,r])+f2(r,[l,r])

差分一下即为 f 1 ( r , [ 1 , r ] ) − f 1 ( r , [ 1 , l − 1 ] ) + f 2 ( r , [ 1 , r ] ) − f 2 ( r , [ 1 , l − 1 ] ) f_1(r,[1,r])-f_1(r,[1,l-1])+f_2(r,[1,r])-f_2(r,[1,l-1]) f1(r,[1,r])f1(r,[1,l1])+f2(r,[1,r])f2(r,[1,l1])

f 1 ( r , [ 1 , r ] ) f_1(r,[1,r]) f1(r,[1,r]) 可以在遍历时 O ( n ) \mathcal O(\sqrt n) O(n ) 枚举因数,累加所有因数之前的出现次数,定义一个桶即可实现。

f 2 ( r , [ 1 , r ] ) f_2(r,[1,r]) f2(r,[1,r]) 可以在上一个操作枚举因数时对该数的每个因数标记一下,即开一个桶记录该数作为因数的出现次数。

以上两部分可提前预处理,计算前缀和,然后跑一边莫队计算贡献,时间复杂度为 O ( n n ) \mathcal O(n\sqrt n) O(nn )

对于 f 1 ( r , [ 1 , l − 1 ] ) f_1(r,[1,l-1]) f1(r,[1,l1]) f 2 r , [ 1 , l − 1 ] f_2{r,[1,l-1]} f2r,[1,l1] 可在上面跑莫队时顺便存下来,进行二次离线,要求 O ( 1 ) \mathcal O(1) O(1) 查询 f 1 f_1 f1 f 2 f_2 f2

考虑二次离线时的修改,这部分要求是 O ( n ) \mathcal O(\sqrt n) O(n ) 级别的。

对于 f 1 f_1 f1,枚举因数,加上贡献即可。

对于 f 2 f_2 f2,考虑根号分治,设阈值 S S S

  • 若当前加入的数 ≥ S \geq S S,暴力跳表,加上贡献。
  • 若当前加入的数 < S < S <S,则将这个提出二次离线这部分,后面处理。

二次离线部分时间复杂度为 O ( n n ) \mathcal O(n \sqrt n) O(nn )

再考虑刚刚遗留下来的一点东西。

p [ i ] [ j ] p[i][j] p[i][j] 表示区间 [ 1 , j ] [1,j] [1,j] 中含有因数 i i i 的数的个数, c [ i ] [ j ] c[i][j] c[i][j] 表示区间 [ 1 , j ] [1,j] [1,j] i i i 的出现次数。

则区间 [ l , r ] [l,r] [l,r] 中含有因数 i i i 的数的个数就是 p [ i ] [ r ] − p [ i ] [ l − 1 ] p[i][r] - p[i][l-1] p[i][r]p[i][l1]

所以 [ 1 , z ] [1,z] [1,z] 中的数 i i i 作为因数出现在 [ l , r ] [l,r] [l,r] 的总次数为 c [ i ] [ z ] × ( p [ i ] [ r ] − p [ i ] [ l − 1 ] ) c[i][z]\times (p[i][r] - p[i][l-1]) c[i][z]×(p[i][r]p[i][l1])

那么枚举 i ∈ [ 1 , S ) i \in [1,S) i[1,S) O ( n ) \mathcal O(n) O(n) 求出 p p p c c c(压掉一维),然后 O ( m ) \mathcal O(m) O(m) 计算贡献即可。

所以总时间复杂度为 O ( n n ) \mathcal O(n \sqrt n) O(nn ),总空间复杂度为 O ( n ) \mathcal O(n) O(n)

32.39s / 75.05MB / 3.92KB C++11 O2 \text{32.39s / 75.05MB / 3.92KB C++11 O2} 32.39s / 75.05MB / 3.92KB C++11 O2

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>

namespace Fread
{
    const int SIZE = 1 << 23;
    char buf[SIZE], *S, *T;
    inline char getchar()
    {
        if (S == T)
        {
            T = (S = buf) + fread(buf, 1, SIZE, stdin);
            if (S == T)
                return '\n';
        }
        return *S++;
    }
}
namespace Fwrite
{
    const int SIZE = 1 << 23;
    char buf[SIZE], *S = buf, *T = buf + SIZE;
    inline void flush()
    {
        fwrite(buf, 1, S - buf, stdout);
        S = buf;
    }
    inline void putchar(char c)
    {
        *S++ = c;
        if (S == T)
            flush();
    }
    struct NTR
    {
        ~NTR()
        {
            flush();
        }
    } ztr;
}

#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#define putchar Fwrite::putchar
#endif

typedef long long ll;

inline int read()
{
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
	{
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

inline void write(ll x)
{
	if(x < 0)
	{
		putchar('-');
		x = -x;
	}
	if(x > 9)
		write(x / 10);
	putchar(x % 10 + '0');
}

const int _ = 5e5 + 7, S = 100;

int n, m, k, a[_], bel[_], siz, mx, cnt[_], flg[_], pre[_];

ll f[_], Ans[_];

struct Query
{
	int l, r, id;
	ll ans;
	inline bool operator < (const Query &t) const
	{
		return bel[l] == bel[t.l] ? (bel[l] & 1 ? r < t.r : r > t.r) : bel[l] < bel[t.l];
	}
} q[_];

struct Node
{
	int l, r, id, x;
	bool operator < (const Node &t) const
	{
		return x < t.x;
	}
} t[_ << 1];

std::vector<int> fac[_];

signed main()
{
	n = read(), m = read();
	siz = sqrt(n);
	for(int i = 1; i <= n; ++i)
	{
		a[i] = read();
		bel[i] = (i - 1) / siz + 1;
		mx = std::max(mx, a[i]);
		if(fac[a[i]].empty())
		{
			for(int j = 1; j * j <= a[i]; ++j)
				if(a[i] % j == 0)
				{
					f[i] += cnt[j];
					flg[j]++;
					if(j * j != a[i])
					{
						f[i] += cnt[a[i] / j];
						flg[a[i] / j]++;
					}
					fac[a[i]].emplace_back(j);
				}
		}
		else
		{
			for(int x : fac[a[i]])
			{
				f[i] += cnt[x];
				flg[x]++;
				if(x * x != a[i])
				{
					f[i] += cnt[a[i] / x];
					flg[a[i] / x]++;
				}
			}
		}
		f[i] += f[i - 1] + flg[a[i]];
		cnt[a[i]]++;
	}
	for(int i = 1; i <= m; ++i)
		q[i] = {read(), read(), i, 0};
	std::sort(q + 1, q + m + 1);
	for(int i = 1, l = 1, r = 0; i <= m; ++i)
	{
		if(r < q[i].r) t[++k] = {r + 1, q[i].r, -i, l - 1}, q[i].ans += f[q[i].r] - f[r], r = q[i].r;
		if(l > q[i].l) t[++k] = {q[i].l, l - 1, i, r}, q[i].ans -= f[l - 1] - f[q[i].l - 1], l = q[i].l;
		if(r > q[i].r) t[++k] = {q[i].r + 1, r, i, l - 1}, q[i].ans -= f[r] - f[q[i].r], r = q[i].r;
		if(l < q[i].l) t[++k] = {l, q[i].l - 1, -i, r}, q[i].ans += f[q[i].l - 1] - f[l - 1], l = q[i].l;
	}
	std::sort(t + 1, t + k + 1);
	memset(f, 0, sizeof f);
	for(int i = 1, x = 0; i <= k; ++i)
	{
		while(x < t[i].x)
		{
			x++;
			for(int v : fac[a[x]])
			{
				f[v]++;
				if(v * v != a[x])
					f[a[x] / v]++;
			}
			if(a[x] > S)
				for(int j = 1; j * a[x] <= mx; ++j)
					f[j * a[x]]++;
		}
		for(int k = t[i].l; k <= t[i].r; ++k)
			if(t[i].id < 0)
				q[-t[i].id].ans -= f[a[k]];
			else
				q[t[i].id].ans += f[a[k]];
	}
	for(int i = 1; i <= S; ++i)
	{
		cnt[0] = pre[0] = 0;
		for(int j = 1; j <= n; ++j)
		{
			cnt[j] = cnt[j - 1] + (a[j] == i);
			pre[j] = pre[j - 1] + (a[j] % i == 0);
		}
		for(int j = 1; j <= k; ++j)
			if(t[j].id < 0)
		 		q[-t[j].id].ans -= (ll)cnt[t[j].x] * (ll)(pre[t[j].r] - pre[t[j].l - 1]);
		 	else
		 		q[t[j].id].ans += (ll)cnt[t[j].x] * (ll)(pre[t[j].r] - pre[t[j].l - 1]);
	}
	for(int i = 1; i <= m; ++i)
	{
		q[i].ans += q[i - 1].ans;
		Ans[q[i].id] = q[i].ans;
	}
	for(int i = 1; i <= m; ++i)
		write(Ans[i]), putchar('\n');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值