第k个数(快速排序每次只需要递归区间的一半)
找出一个分界点x使得左边都<=x, 右边都>=x统计左右两边区间中数的个数分别记为SL, SK。
1、当k<=SL时,第k个数一定在左边,递归处理左边区间
2、当k>SL时,第k个数一定在右边,递归处理右边区间,整个区间第K小的数即为右边区间k-SL个数
时间复杂度为O(n)每次都处理区间一半,快速选择算法,保证第K个数一定在区间里面
#include <iostream>
using namespace std;
const int N = 100010;
int q[N];//总的数
int quick_sort(int q[], int l, int r, int k)
{
if (l >= r) return q[l];//保证k在区间里面
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);// while(q[--i] < x)
do j -- ; while (q[j] > x);// while(q[++j] > x)
if (i < j) swap(q[i], q[j]);
}
if (j - l + 1 >= k) return quick_sort(q, l, j, k);//j - l + 1为左边区间的数的总数
else return quick_sort(q, j + 1, r, k - (j - l + 1));
}//模板
int main()
{
int n, k;
scanf("%d%d", &n, &k);//排序总数和找到的第k个数
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
cout << quick_sort(q, 0, n - 1, k) << endl;
return 0;
}
/*递归结束ij只有以下两种情况:
1、i=j 2、i = j + 1*/
逆序对的数量
逆序对:从所有数中暗器那后顺序选两个数,前面的数比后面的数大即为逆序对
归并排序流程:
1、[L, R] => [L, mind], [mid + 1, R]
2、递归排序[L, mid]和 [mid + 1, R]
3、归并,将左右两个有序序列合并成一个有序序列
求逆序对思想:
1、基于分治的思想将两个逆序对分成三种情况,两个数同时出现在右半边,左半边,一边一个
(1)左半边内部的逆序对数量:merge_sort(L, mid)
(2)右半边内部的逆序对数量:merge_sort(mid + 1, R)
(3)统计左半边里面有多少个数比右半边的数大,用两个指针分别指向左半边和右半边,找出比q[j]大的q[i]后每次输出q[j]都加上mid - j + 1
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], tmp[N];//tmp临时数组用来存储中间排序的结果
LL merge_sort(int q[], int l, int r)
{
if (l >= r) return 0;//l = r类似
int mid = l + r >> 1;//+优先级比>>高故不需要加(),该表达式等价于(l + r) / 2
LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r); //求左半边和右半边逆序对的个数
//归并的过程,计算第三种情况下逆序对的个数
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else
{
res += mid - i + 1;
tmp[k ++ ] = q[j ++ ];
}
//扫尾(最多只进行一个循环,数组有一部分已经没有数了
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
//将临时数组中的数据返回给原来的数组
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
return res;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
cout << merge_sort(a, 0, n - 1) << endl;
return 0;
}
数的三次方根(浮点数的二分)
首先确定区间 0 ~ max(1, x),答案必须在区间里面,取m = (l + r) / 2如果m的三次方大于等于x,则x的三次方根在左半边区间更新区间r = m
否则更新区间l = m
#include <iostream>
using namespace std;
int main()
{
double x;
cin >> x;
double l = -10000, r = 10000;
while (r - l > 1e-8)
{
double mid = (l + r) / 2;
if (mid * mid * mid >= x) r = mid;//左半边区间
else l = mid;
}
printf("%.6lf\n", l);
return 0;
}
前缀和
一维前缀和:
核心公式
1.Si = a1 + a2 + ... + ai
2.sum(L, R) = aL + aL + 1 + aL + 2 + ... + aR = SR - SL-1
方法:
1.预处理前缀和数组
2.用公式求区间和
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];//a原数组,s前缀和数组
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);//i = 1,保证计算前缀和时下标不越界
//求前缀和,凡是下标中出现减一的情况在循环时循环变量都是从1开始
for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];
while (m -- )
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l - 1]); // 区间和的计算
}
return 0;
}
子矩阵的和(二位前缀和)
S[i,j]为ij左上角格子的和
核心操作:
1、计算(x1,y1)(x2,y2)这一子矩阵中所有数的和 S[x2,y2] - S[x1 - 1,y2] - s[x2, y1 - 1] + S[x1 -1,y1 - 1]
2、计算S[i,j]按行进行计算 S[i,j] = S[i - 1,j] + S[i, j - 1] - S[i - 1, j - 1] + a[i,j]
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
//初始化前缀和数组
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (q -- )
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}
差分(一维差分)
给定a[1],a[2]...a[n]数组构造差分数组b[N]使得a[i] = b[1] + b[2] + ... + b[i]
核心操作:首先先将a数组全部看成为零,再将a[L ~ R]全部加上C, 等价于:b[L] += C, b[R + 1] -= C
该操作影响:1、a[1 ~ L - 1]无影响 2、a[L ~ R]加上了C 3、a[R + 1 ~ N]无影响作用将O(n)的时间复杂度变为O(1)
即在全是零的数组上进行n步的插入操作
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) insert(i, i, a[i]);
while (m -- )
{
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
for (int i = 1; i <= n; i ++ ) a[i] = a[i - 1] + b[i];
for (int i = 1; i <= n; i ++ ) printf("%d ", a[i]);
return 0;
}
差分矩阵(二位差分)
给定原矩阵a[i,j],构造差分矩阵b[i,j],使得a[][]是b[][]的二位前缀和
差分核心操作(插入):给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有数a[i,j]都加上C
对于差分数组b的影响:(两重循环改变为只改变四个数)
b[x1, y1] += C b[x1, y2 + 1] -= C b[x2 + 1, y1] -=C b[x2 + 1, y2 + 1] += C
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
insert(i, j, i, j, a[i][j]);
while (q -- )
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ ) printf("%d ", a[i][j]);
puts("");
}
return 0;
}
代码转载于
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/39800/
来源:AcWing