基础算法

基础算法

目录(点击跳转)

差分

差分是前缀和的逆运算,运用于区间修改和询问。当对数据集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;
}
posted @ 2025-06-13 01:14  HLAIA  阅读(16)  评论(0)    收藏  举报