题目一
方法一
归并分治
代码:
# include <stdio.h>int arr[100];
int help[100];int n;//归并分治
// 1.统计i、j来自 l~r 范围的情况下,逆序对数量
// 2.统计完成后,让arr[l...r]变成有序的
int f(int l, int r)
{if (l == r)return 0;int m = (l + r) / 2;return f(l, m) + f(m+1, r) + merge(l, m, r);
}int merge(int l, int m, int r)
{//i 来自 l...m//r 来自 m+1....r//统计有多少逆序对 int ans = 0;int j = r;for (int i=m; i>=l; --i){while (j >= m+1 && arr[i] <= arr[j])j = j - 1;ans = ans + j - m;}//左右部分合并,整体变有序,归并排序的过程 int k = l;int a = l;int b = m + 1;while (a <= m && b <= r){if (arr[a] <= arr[b]){help[k] = arr[a];a = a + 1;}else{help[k] = arr[b];b = b + 1;}k = k + 1;}while (a <= m){help[k] = arr[a];k = k + 1;a = a + 1;}while (b <= m){help[k] = arr[b];k = k + 1;b = b + 1;}for (i = l; i <= r; ++i)arr[i] = help[i];return ans;
}int main()
{scanf("%d", &n);for (int i=0; i<n; ++i)scanf("%d", &arr[i]);int ans = f(0, n-1);
}
方法二
树状数组
我们假设一个数组arr
建立一个词频数组
用于记录数字出现的次数
因为arr数组中最大的数字为100
所以只要建立一个长度为100的词频数组即可
我们对原数组从右往左进行考虑
先考虑arr[8](也就是2)
对于逆序对,i < j,并且arr[i] > arr[j]
因此我们只要统计小于2的数,在词频数组中的和即可
也就是词频数组中下标2之前的前缀和
在统计完sum后,再将2添加进词频数组中
这时,我们就可以把词频数组转为树状数组
以上的操作就变为了
单点修改和区间查询
注意:
此时要考虑树状数组长度的问题!!!
因为词频数组的长度是由原数组最大值决定的,可能会出现过大的情况
此时就要进行离散化处理!
我们可以假设另外一个数组help
将原数组按照从小到大的顺序填入help数组中
并进行去重(可以使用双指针)
然后通过help数组对arr数组进行修改
利用二分
找到原数组中的数字在help数组中的下标位置
并将该数字修改为help数组中的下标的值
再对此时的arr数组进行词频统计建立树状数组
代码:
# include <stdio.h>
# include <stdlib.h>
int arr[100];
int sort[100];
int tree[100];int n, m;int cmp(const void* a, const void* b)
{int p1 = *(int*)a;int p2 = *(int*)b;return p1 - p2;
}int lowbit(int x)
{return x & (-x);
}void add(int i, int v)
{while (i <= m){tree[i] = tree[i] + m;i = i + lowbit(x);}
}int sum(int i)
{int ans = 0;while (i > 0){ans = ans + tree[i];i = i - lowbit(i;)}return ans;
}// 给定原始值v
// 返回sort数组中1~m的下标
int rank(int v)
{int l = 1;int r = m;int mid;int ans = 0;while (l<=r){mid = (l + r) / 2;if (sort[mid] >= v){ans = mid;r = mid - 1;}else{l = mid + 1;}}return ans;
}int main()
{scanf("%d", &n);for (int i = 1; i <= n; ++i){scanf("%d", arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(int), n, cmp);m = 1;for (int i=2; i <= n; ++i){if (sort[i] != sort[m]){m = m + 1;sort[m] = sort[i];}}int ans = 0;for (int i=1; i<=n; ++i)arr[i] = rank(arr[i]);for (int i=n; i>=1; --i){//增加当前数字的词频 add(arr[i], 1);// 右边有多少数字是 <= 当前数值 - 1 ans = ans + sum(arr[i]-1);}printf("%d", ans);
}
两个方法的区别:
归并分治实现:无需离散化代码、使用空间少、常数时间优良,不能实时查询,只能是离线的批量过程
树状数组实现:需要离散化代码、使用空间多、常数时间稍慢,可以实时查询
题目二(典型)
我们建立两个数组up1, up2
up1[ i ]:以下标 i 做结尾的升序的只有一个元素的数量
up2[ i ]:以下标 i 做结尾的升序的含有两个元素的数量
假设数组arr为:
从左往右进行处理
我们加入第一个数字为arr[1] = 1
先判断以 1 做结尾的升序的含有三个元素的数量
我们发现该值就是在up2数组中小于1的前缀和
然后再更新以 1 做结尾的升序的含有两个个元素的数量(也就是up2[1])
我们发现该值就是在up1数组中小于1的前缀和
找到该值后,填入up2[1]
更新以 1 做结尾的升序的含有一个元素的数量(也就是up1[1])
依次对 arr 数组从左往右进行遍历
因此,我们先对数组中的元素进行离散化处理
然后从左往后按照上述步骤进行
代码:
# include <stdio.h>
# include <string.h>
//记录原数组
int arr[100];//建立去重离散化数组
int sort[100];//维护信息:up1
//tree1不是up1数组,是up1数组的树状数组
int tree1[100]; //维护信息:up2
//tree2不是up2数组,是up2数组的树状数组
int tree2[100];int n, m;int cmp(const void* a, const void* b)
{int* p1 = (int*)a;int* p2 = (int*)b;return *p1 - *p2;
}int lowbit(int x)
{return x & (-x);
}void add(int* tree, int i, int v)
{while (i <= m){tree[i] = tree[i] + v;i = i + lowbit(i);}
}int sum(int* tree, int i)
{int ans = 0;while (i > 0){ans = ans + tree[i];i = i - lowbit(i);}return ans;
}int rank(int x)
{int l = 1;int r = m;int mid;int ans = 0;while (l <= r){mid = (l + r) / 2;if (sort[mid] >= v){ans = mid;r = mid - 1;}else l = mid + 1;}return ans;
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(int), n, cmp);m = 1;for (int i=2; i<=n; ++i){if (sort[m] != sort[i]){m = m + 1;sort[m] = sort[i];}}for (int i=1; i<=n; ++i)arr[i] = rank(arr[i]);int ans = 0;for (int i=1; i<=n; ++i){ans = ans + sum(tree2, arr[i] - 1);add(tree1, arr[i], 1);add(tree2, arr[i], sum(tree2, arr[i] - 1));} printf("%d", ans);
}
题目三
我们建立一个结构
用于存储以下标 i 为结尾的子序列的最大长度和个数
# include <stdio.h>struct node
{int len; //长度 int num; //个数
};//不是该结构数组,是该结构体的树状数组
//用于维护一个范围的最大长度和个数
struct node tree[100];int arr[100];
int sort[100];int n, m;//查询结尾数值<=i的所有递增子序列中
//长度最大的递增子序列长度是多少,赋值给maxlen和maxnum
int maxlen, maxnum;int lowbit(int x)
{return x & (-x);
}//以 i 这个值结尾的最长递增子序列达到了len长度
//并且这样的最长递增子序列的数量达到了cnt个
void add(int i, int l, int cnt)
{while (i <= m){if (tree[i].len == l)tree[i].num = tree[i].num + cnt;else if (tree[i].len < l){tree[i].len = l;tree[i].num = cnt;}i = i + lowbit(i);}
}//结尾数值 <= i 的情况下,最长递增子序列的长度和个数为多少
void query(int x)
{maxlen = 0;maxnum = 0;while (x > 0){if (tree[x].len == maxlen)maxnum = tree[x].num + maxnum;else if (tree[x].len > maxlen){maxlen = tree[x].len;maxnum = tree[x].num;}x = x - lowbit(x);}
}int rank(int x)
{int l = 1;int r = m;int mid;int ans;while (l <= r){mid = (l+r) / 2;if (sort[mid] >= x){r = mid - 1;ans = mid;}elsel = mid + 1;}return ans;
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(sort), n, cmp);int m = 1;for (int i=2; i<=n; ++i){if (sort[m] != sort[i]){sort[m] = sort[i];m = m + 1;}}memset(tree, 0, sizeof(tree));int y;for (int i=1; i<=m; ++i){y = rank(arr[i]);query(y-1);add(y, maxlen+1, max(1, maxnum));}int ans = query(m);printf("%d", ans);
}
题目四
本题转化是非常巧妙的
我们首先需要对查询进行排序
按照 r 的大小进行排序
如图:
一个虚拟数组cnt[](之所以说它是虚拟数组,因为可以用树状数组实现想要的操作,不需要真的定义这么一个数组)。
map[i] 记录 i 位置上的数上一次出现的位置
当遍历到原数组的下标 i 时,cnt[i]+1,如果 i 位的这个数之前出现过,即存在map[i],则将cnt[map[i]]-1(在遍历到i之前已经遍历过last[i]位置了,因此cnt[map[i]]=1,所以这样操作下来,cnt[map[i]]=0),不会对当前要求的结果造成影响
比如序列:2,3,4,3,4,2
当右边界为5时(下标从1开始),对应的cnt[]值分别为:1,0,0,1,1
所以区间【1,5】内不同的数字个数=3,区间【2,5】内不同的数字个数=2,区间【4,5】内不同的数字个数=2(树状数组累加和求解)
因为右边界一定,所以我们必须要确保选择的1是尽可能靠近右边界,确保答案正确
# include <stdio.h>
# include <string.h>
struct node
{int l;int r;int k;
};int arr[100];struct node query[100];int ans[100];int map[100];int tree[100];int n, m;int compare(const void*a, const void* b)
{struct node* p1 = (struct node*)a;struct node* p2 = (struct node*)b;return *p1.r - *p2.r;
}int lowbit(int x)
{return x & (-x);
}void add(int i, int v)
{while (i <= n){tree[i] = tree[i] + v;i = i + lowbit(i);}
}int q(int x)
{int ans = 0;while (x > 0){ans = tree[x] + ans;x = x - lowbit(x);}return ans;
}int range(int l, int r)
{return sum(r) - sum(l-1);
}void cmp(void)
{qsort(query, sizeof(struct node), m, compare);int q = 1; //遍历询问 int l;int r;int k; for (int q=1; q <= m; ++i){r = query[q].r;for (; s<=r; ++s){int color = arr[s];if (map[color] != 0)add(map[color], -1)add[s, 1];map[color] = s;}l = query[q].l;k = query[q].k;ans[i] = range(l, r);}
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);}scanf("%d", &m);for (int i=1; i<=m; ++i){scanf("%d %d", &query[i].l, &query[i].r);query[i].k = i;}cmp();for (int i=1; i<=m; ++i)printf("%d", ans[i]);
}