题解 | #排列询问#
排列询问
http://www.nowcoder.com/practice/99062f0877e047bb8c7374d241268a8b
题目描述
牛妹有一个长度为n的排列p,她有q个询问。
每个询问包含l1,r1,l2,r2.她想知道从[l1,r1]中选取x,[l2,r2]中选取y,有多少组(x,y)满足min(x,y)==gcd(x,y)?
返回一个vector代表对这q个询问的答案
方法一:暴力求解
求解思路
对于本题目的求解,我们可以采用暴力循环的方式进行求解,我们遍历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> myres(q, 0); for(int i = 0; i < q; i++) { for(int j = l1[i]; j <= r1[i]; j++) { //遍历第一个区间 int i1 = p[j]; for(int k = l2[i]; k <= r2[i]; k++) { //遍历第二个区间 int j1 = p[k]; if(i1 % j1 == 0 || j1 % i1 == 0) // 最小与gcd比较是否相等 myres[i]++; } } } return myres; // 返回结果 } };
复杂度分析
时间复杂度:两层循环,因此时间复杂度为( )
空间复杂度:常数级内存空间,因此空间复杂度为
方法二:贪心思想求解
求解思路
对于本题我们也可以使用贪心的思想进行求解,我们可以把拆分后的询问按照右端点大小进行排序,并且记录之前在数组V中的结果,并且从左到右枚举右端点的位置,把满足条件数对中右端点为i的进行记录,并且计算gcd值,按照上述思路,即可得到本问题的答案。
解题代码
const int N = 2e5 + 10; int pos[N], c[N]; vector<int> v[N]; struct node { int l, r, id, fg; node(){} node(int a, int b, int c, int d) : l(a), r(b), id(c), fg(d){} bool friend operator < (node a, node b) // 为方便题目的解答,重载操作符 { //按照右边排序 return a.r < b.r; } }Q[N * 4]; class Solution { public: int Sum(int k) { int sum = 0; //.记录结果 for(; k > 0; k -= k & (-k)) sum += c[k]; return sum; } void add(int k, int num) // 定义add方法 { for(; k < N; k += k & (-k)) c[k] += num; } vector<int> PermutationQuery(int n, int q, vector<int>& p, vector<int>& l1, vector<int>& r1, vector<int>& l2, vector<int>& r2) { vector<int> myres(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 total = 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[++total] = {b + 1, c - 1, i , 1}; // 公式的几个数 Q[++total] = {a, c - 1, i, -1}; Q[++total] = {b + 1, d, i, -1}; Q[++total] = {a, d, i, 1}; } sort(Q + 1, Q + 1 + total); // 快排 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 <= total && Q[pos].r == i) { //处理拆分后询问的右端点为i的 myres[Q[pos].id] += Q[pos].fg * (Sum(Q[pos].r) - Sum(Q[pos].l - 1)); pos++; } } return myres; // 返回结果 } };
复杂度分析
时间复杂度:遍历的时候一层循环加两次贪心时间(logn),因此时间复杂度为( )
空间复杂度:对数组进行存储,因为涉及到左右端点,因此空间复杂度为
算法 文章被收录于专栏
算法题的题解以及感受