基础算法
目录(点击跳转)
差分
差分是前缀和的逆运算,运用于区间修改和询问。当对数据集A的某个区间做修改时,引入差分数组D,只需要修改这个区间的端点,就能记录整个区间的修改,这个修改的复杂度为O(1) 。当所有修改都完成后,再利用差分数组计算出新的A。
差分数组D[]的定义是:D[k] = a[k] - a[k-1],a[]是原数组
显然,a[i] = D[1]+D[2]+...+D[i],a[]是D[]的前缀和
例如,把数组区间[L,R]内的每个元素都加上d,只需要对应的差分数组D[]做以下操作:
把D[L]加上d:D[L] += d;
把D[R+1]减去d:D[R+1] -= d;
D[]只会修改区间[L,R]内的值,不会修改到区间外的,要查询a[i]时,做一个D[i]的前缀和即可
以HDU 1556 为例
题目

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+1;
int a[maxn],D[maxn];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
while(cin >> n){
int l,r;
memset(a,0,sizeof(a));
memset(D,0,sizeof(D));
for(int i=1;i<=n;i++){
cin >> l >> r;
D[l]++;D[r+1]--;
}
for(int i=1;i<=n;i++){
a[i] = a[i-1] + D[i];
cout << a[i] << ' ';
}
cout << '\n';
}
return 0;
}
二分
Please remember:使用二分一定有一个前提----序列已经排序,且排序方式与使用的函数一致
C++STL标准模板库提供了二分查找函数,时间复杂度为 $ O(\log n) $
lower_bound(a.begin(),a.end(),x)
找到序列 \([First\ ,\ last)\) 中第一个 \(\geq x\) 的数,并返回该数的迭代器;找不到就返回a.end()。
如果要修改这个数的值为b,可以直接
*lower_bound(a.begin(),a.end(),x) = b;
如果序列是降序排列,要找到第一个 \(\leq x\) 的数,可以用
lower_bound(a.begin(),a.end(),x,greater<int>())
upper_bound(a.begin(),a.end(),x)
找到序列 \([First\ ,\ last)\) 中第一个 \(>x\) 的数,并返回该数的迭代器;找不到就返回a.end()。
如果要修改这个数的值为b,可以直接
*upper_bound(a.begin(),a.end(),x) = b;
如果序列是降序排列,要找到第一个 \(<x\) 的数,可以用
upper_bound(a.begin(),a.end(),x,greater<int>())
逆序对
简单的求逆序对的方法:冒泡排序,时间复杂度$ O(n^2) $
直接给代码,不解释了:
#include <bits/stdc++.h>
using namespace std;
int a[10] = {3,5,7,9,2,1,4,6,8,10};
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n = 10, cnt = 0;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
if(a[j] < a[i]){
swap(a[i],a[j]);
cnt++;
}
cout << cnt << '\n';
return 0;
}
更好的求法:树状数组(逆序对+离散化)
也是好久没有接触树状数组了,最近一次出现树状数组还是德叔在重庆赛上秒H题
用树状数组求逆序对的个数,需要用到一个技巧:把数字看成树状数组的下标
例如,序列{5,4,2,6,3,1},对应a[5]、a[4]、a[2]、a[6]、a[3]、a[1]
每处理一个数字,树状数组下标对应的元素数值加1,统计前缀和,就是逆序对的数量
倒序处理和正序处理都可以,我一般用倒序处理
倒序:
用树状数组倒叙处理序列,当前数字的前一个数的前缀和,即为以该数为较大数的逆序对的个数。
例如,还是序列{5,2,4,6,3,1},倒叙处理:
数字1,把a[1]+1,计算a[1]的前缀和sum(0),逆序对数量ans += sum(1-1) = 0
数字3,把a[3]+1,计算a[3]的前缀和sum(2),逆序对数量ans += sum(3-1) = 1
数字6,把a[6]+1,计算a[6]的前缀和sum(5),逆序对数量ans += sum(6-1) = 3
......
此外,还需要用到离散化来处理空间问题。
离散化就是把原来的数字用它们的相对大小替代,而它们的顺序仍然不变,不影响逆序对的计算。例如,{1,20000,10,30,890000000},离散化后变为 {1,4,2,3,5},新旧序列的逆序对数量是一样的
以一道模板题为例,洛谷P1908
题目描述
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 \(a_i>a_j\) 且 \(i<j\) 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
输入格式
第一行,一个数 \(n\),表示序列中有 \(n\) 个数。
第二行 \(n\) 个数,表示给定的序列。序列中每个数字不超过 \(10^9\)。
输出格式
输出序列中逆序对的数目。
输入输出样例
样例1
6
5 4 2 6 3 1
输出
11
说明/提示
对于 \(25\%\) 的数据,\(n \leq 2500\)。
对于 \(50\%\) 的数据,\(n \leq 4 \times 10^4\)。
对于所有数据,\(1 \leq n \leq 5 \times 10^5\)。
请使用较快的输入输出。
#include <bits/stdc++.h>
#define lowbit(x) (x & (-x))
using namespace std;
using ll = long long;
const int maxn = 5e5+5;
int tree[maxn],dc[maxn],n;
struct node{
int val,index;
}a[maxn];
bool cpa(node x,node y){
if(x.val == y.val) return x.index < y.index;
return x.val < y.val;
}
void update(int x,int d){
while(x <= n){
tree[x] += d;
x += lowbit(x);
}
}
int sum(int x){
int ans = 0;
while(x > 0){
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i].val;
a[i].index = i;
}
sort(a+1,a+n+1,cpa);
for(int i=1;i<=n;i++) dc[a[i].index] = i;
ll cnt = 0;
for(int i=n;i>=1;i--){
update(dc[i],1);
cnt += sum(dc[i]-1);
}
cout << cnt << '\n';
return 0;
}

浙公网安备 33010602011771号