思路:
题目的主要信息:
- p有n个数,一共有q次询问
- 每次询问从中选取,中选取,求有多少组满足,每个区间都是p的下标,从0开始
方法一:暴力法
具体做法:
遍历q次查询,遍历每个第一个区间的下标,与第二个区间的下标两两组合,判断是否满足条件。
class Solution { public: vector<int> PermutationQuery(int n, int q, vector<int>& p, vector<int>& l1, vector<int>& r1, vector<int>& l2, vector<int>& r2) { vector<int> res(q, 0); for(int i = 0; i < q; i++){ for(int j = l1[i]; j <= r1[i]; j++){ //遍历第一个区间 int a = p[j]; for(int k = l2[i]; k <= r2[i]; k++){ //遍历第二个区间 int b = p[k]; if(a % b == 0 || b % a == 0) //最小与gcd比较是否相等 res[i]++; } } } return res; } };
复杂度分析:
- 时间复杂度:,次询问,每次询问都是两层遍历,最坏情况下每个区间长度都是
- 空间复杂度:,res属于返回必要空间,不属于额外空间
方法二:贪心
具体做法:
对于任意一个排列p,满足的最多只有对,我们可以在的时间内暴力的求出这些符合条件的组合,记录到数组v中,然后就是在区间内选与。
如果我们以表示从区间中选取不同的满足上述条件数对的数量,于是依据容斥原理我们求这个就可以了:。
我们可以把拆分后的询问Q按照右端点大小进行排序,并且之前记录在数组v中的结果也按右端点排序。从左到右枚举右端点的位置,把满足条件数对中右端点为的,在树状数组(即v)中左端点位置处的值+1,如果处理后右端点刚好为i,利用上述公式计算值,代码中是Sum函数计算的减价。
class Solution { public: int pos[200010], s[200010]; vector<int> v[200010]; struct node{ int l, r, id, flag; node(){l = 0, r = 0, id = 0, flag = 0;} node(int a, int b, int c, int d) : l(a), r(b), id(c), flag(d){} bool friend operator <(node a, node b) { //按照右边排序 return a.r < b.r; } }Q[200010 * 4]; //查询的结构 int N = 200010; void add(int k, int num) { for(; k < N; k += k & (-k)) s[k] += num; } int Sum(int k) { int sum = 0; for(; k > 0; k -= k & (-k)) sum += s[k]; return sum; } vector<int> PermutationQuery(int n, int q, vector<int>& p, vector<int>& l1, vector<int>& r1, vector<int>& l2, vector<int>& r2) { vector<int> res(q, 0); for(int i = 0; i < n; i++) //记录位置 pos[p[i]] = i + 1; for(int i = 1; i <= n; i++) {//处理nlogn组满足要求的 int val = i + i; while(val <= n) { int l = pos[val]; int r = pos[i]; if(l > r) swap(l,r); v[r].push_back(l); //存储nlogn对 val += i; } } int all = 0; for(int i = 0; i < q; i++) {//q次询问 int a = l1[i], b = r1[i], c = l2[i], d = r2[i]; a++, b++, c++, d++; if(c - b > 1) Q[++all] = {b + 1, c - 1, i , 1}; //公式的几个数 Q[++all] = {a, c - 1, i, -1}; Q[++all] = {b + 1, d, i, -1}; Q[++all] = {a, d, i, 1}; } sort(Q + 1, Q + all + 1); //排序 int pos = 1; for(int i = 1; i <= n; i++) {//从左到右枚举右端点位置 for(int j = 0; j < v[i].size(); j++) add(v[i][j], 1);//把右端点为i的,在树状数组中左端点位置处的值+1 while(pos <= all && Q[pos].r == i) {//处理拆分后询问的右端点为i的 res[Q[pos].id] += Q[pos].flag * (Sum(Q[pos].r) - Sum(Q[pos].l - 1)); pos++; } } return res; } };
复杂度分析:
- 时间复杂度:,最坏时间为处理的时候遍历,里面两个,外面还有一个次询问的遍历
- 空间复杂度:,最多的空间是存储那对满足要求的数组