2023 hdu 第6场 1002 Pair Sum and Perfect Square

文章描述了一种算法,用于解决给定长度为n的排列p的查询问题,查询是在指定区间内有多少对(i,j)满足p_i+p_j为平方数。通过枚举和离线分块处理,将时间复杂度优化到O(n√n)。

Problem Description

给出一个长度为 nnn 的排列 ppp ,进行 QQQ 次查询。

每次查询给出一个区间 L,RL,RL,R (1≤L≤R≤n1 \le L \le R \le n1LRn),询问有多少对 (i,ji,ji,j) (L≤i<j≤RL\le i < j \le RLi<jR)满足 pi+pjp_{i} + p_{j}pi+pj 是平方数。

Input

第一行包含一个整数 TTT (T≤5T \le 5T5),表示测试组数。

对于每组测试,输入 Q+3Q+3Q+3 行。

第一行包含一个整数 n (1≤n≤1051 \le n \le 10^51n105),表示排列 ppp 的长度。

第二行包含 nnn 个整数 p1,p2,…,pnp_{1},p_{2},\ldots,p_{n}p1,p2,,pn (1≤pi≤n1 \le p_{i} \le n1pin),表示排列 ppp 的各个元素。

第三行包含一个整数 QQQ (1≤Q≤1051 \le Q \le 10^51Q105),表示询问次数。

接下来 QQQ 行每行包含两个整数 LLLRRR (1≤L≤R≤n1 \le L \le R \le n1LRn),表示查询区间。

Output

对于每次查询输出对应答案。

Solution

首先只考虑如何算出 [1,n][1,n][1,n] 的对数,

由于 pip_ipi 是一个排列,所以 pi+pjp_i+p_jpi+pj 最大值为 2n−12n-12n1 ,那么对于每个 pjp_jpj 我们可以 O(n)O(\sqrt{n})O(n) 枚举出 1≤i<j1 \le i<j1i<j 使 pi+pjp_i+p_jpi+pj 为平方数的个数。

		for (int j = 2; j * j < 2 * n; j++) {
			int x = j * j - a[i];
			if (x >= 1 && x <= n && p[x] < i)sum[id[p[x]]]++, c[p[x]]++;
		}

此时我们再加入多个区间查询,可以发现这就是一个二维数点的问题,那么我们就可以进行离线分块处理,将每次查询的 L,RL,RL,RRRR 为关键词存下。

	for (int i = 0; i < m; i++) {
		int l, r;
		cin >> l >> r;
		q[r].emplace_back(l, i);
	}

i∈[1,n]i \in [1,n]i[1,n] 顺序做的同时当存在 i==Ri==Ri==R 时利用分块暴力跑 [L,R][L,R][L,R] ,最终时间复杂度在 O(nn)O(n \sqrt{n})O(nn)

	for (int i = 1; i <= n; i++) {
		for (int j = 2; j * j < 2 * n; j++) {
			int x = j * j - a[i];
			if (x >= 1 && x <= n && p[x] < i)sum[id[p[x]]]++, c[p[x]]++;
		}
		for (auto [l, j] : q[i]) {
			for (int k = l; k <= min(n, id[l]*len); k++)ans[j] += c[k];
			for (int k = id[l] + 1; k <= decn; k++)ans[j] += sum[k];
		}
	}

Code

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
void solve() {
	int n;
	cin >> n;
	int decn = 0, len = sqrt(n);
	vector<int>a(n + 1), p(n + 1);
	vector<int>id(n + 1), c(n + 1), sum(n + 1);
	vector<pair<int, int>>q[n + 1];
	for (int i = 1; i <= n; i++)cin >> a[i], p[a[i]] = i;
	for (int l = 1, r; l <= n; l = r + 1) {
		++decn;
		r = min(l + len - 1, n);
		for (int i = l; i <= r; i++)id[i] = decn;
	}
	int m;
	cin >> m;
	vector<int>ans(m);
	for (int i = 0; i < m; i++) {
		int l, r;
		cin >> l >> r;
		q[r].emplace_back(l, i);
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 2; j * j < 2 * n; j++) {
			int x = j * j - a[i];
			if (x >= 1 && x <= n && p[x] < i)sum[id[p[x]]]++, c[p[x]]++;
		}
		for (auto [l, j] : q[i]) {
			for (int k = l; k <= min(n, id[l]*len); k++)ans[j] += c[k];
			for (int k = id[l] + 1; k <= decn; k++)ans[j] += sum[k];
		}
	}
	for (auto x : ans)cout << x << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--)solve();
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值