void quick_sort(int a[], int l, int r)
{
if(l >= r) return;
int i = l - 1, j = r + 1, x = a[l + r >> 1];
while(i < j)
{
while(a[++i] < x);
while(a[--j] > x);
if(i < j) swap(a[i], a[j]);
}
quick_sort(a, l, j);
quick_sort(a, j + 1, r);
}
应用:快速选择 第k个数
//如果第k个数在左就在左区间找,在右就在右区间找
//由此保证答案在区间中
int quick_sort(int a[], int l, int r, int k)
{
//区间长度为1时就是答案
if(l == r) return a[l];
int i = l - 1, j = r + 1, x = a[l + r >> 1];
while(i < j)
{
while(a[++i] < x);
while(a[--j] > x);
if(i < j) swap(a[i], a[j]);
}
//一趟快排后 前j个的数有哪些已经确定
int lcnt = j - l + 1;
if(k <= lcnt) return quick_sort(a, l, j, k);
return quick_sort(a, j + 1, r, k - lcnt);
}
int tmp[N];
void merge_sort(int a[], int l, int r)
{
//递归出口 区间长度为0或1时已经有序
if(l >= r) return;
//先把左右区间都排好序
int mid = (l + r) / 2;
merge_sort(a, l, mid);
merge_sort(a, mid + 1, r);
//再有序合并两个区间到辅助数组
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r)
{
if(a[i] <= a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
//扫尾
while(i <= mid) tmp[k++] = a[i++];
while(j <= r) tmp[k++] = a[j++];
//再把辅助数组拷贝给原数组
for(int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
}
应用:逆序对的个数
int tmp[N];
int merge_sort(int a[], int l, int r)
{
//递归出口 区间长度为0或1时逆序对个数为0
if(l >= r) return 0;
int mid = l + r >> 1;
//先分别求左右区间内部的逆序对个数
int res = merge_sort(a, l, mid) + merge_sort(a, mid + 1, r);
//再求两个区间之间的逆序对个数
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r)
{
if(a[i] <= a[j]) tmp[k++] = a[i++];
else
{
tmp[k++] = a[j++];
res += mid - i + 1;
}
}
while(i <= mid) tmp[k++] = a[i++];
while(j <= r) tmp[k++] = a[j++];
for(int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
//最后返回总共的逆序对个数
return res;
}
根据某种性质,将一段区间分成有这个性质和没有这个性质的两段,二分出的就是这两段的边界。
因此有单调性一定可以二分,没单调性也可能可以二分。
//第一种写法
while(l < r)
{
int mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
//第二种写法
while(l < r)
{
int mid = (l + r + 1) / 2;//(1)
if(check(mid)) l = mid;//(2)
else r = mid - 1;
}
//注意(1)(2)
//当 r = l + 1 时,如果 mid = (l + r) / 2 = l => l = mid = l,就会死循环
//因此改为 mid = (l + r + 1) / 2
二分一定有答案,可以根据二分答案判断题目是否有解。
double eps = 1e-8;//经验值,一般比题目保留位数多两位
while(r - l > eps)
{
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
//r - l <= eps 时,就认为 l 或 r 是答案了
建议下标从 1 1 1 开始
//定义
a[1] + ... + a[i] = s[i]
//核心操作
s[i] = s[i - 1] + a[i]
a[l] + ... + a[r] = s[r] - s[l - 1]
//定义
s[i][j] = 第i行j列格子左上部分所有元素的和
//以(x1, y1)为左上角,(x2, y2)为右下角的矩阵的所有元素的和
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
建议下标从 1 1 1 开始
//差分就是前缀和的逆运算
b[i] = a[i] - a[i - 1]
//核心操作
//给区间[l, r]中的每个数加上c
//只需对差分数组b这样操作
b[l] += c, b[r + 1] -= c
//然后对b求前缀和就是操作后的原数组
//给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
设最低位为第 0 0 0 位
//右移 k 位,再 & 1
x >> k & 1
返回二进制最后一个1
//eg: x = 1110 => -x = 0010
//x & -x = 0010
int lowbit(int x)
{
return x & -x;
}
vector<int> v;
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
#include
using namespace std;
stack<int> num;//操作数栈
stack<char> op;//运算符栈
void eval()
{
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
int res;
if(c == '+') res = a + b;
else if(c == '-') res = a - b;
else if(c == '*') res = a * b;
else res = a / b;
num.push(res);
}
int main()
{
//运算符优先级表
unordered_map<char, int> p{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
string s; cin >> s;
for(int i = 0; i < s.size(); i++)
{
if(isdigit(s[i]))
{
int x = 0, j = i;
while(j < s.size() && isdigit(s[j]))
x = x * 10 + s[j++] - '0';
i = j - 1;
num.push(x);
}
else if(s[i] == '(') op.push(s[i]);
else if(s[i] == ')')
{
while(op.top() != '(') eval();
op.pop();
}
else
{
while(!op.empty() && op.top() != '(' && p[op.top()] >= p[s[i]]) eval();
op.push(s[i]);
}
}
while(!op.empty()) eval();
cout << num.top() << '\n';
}
常见题型:
以求每个数左边第一个比它小的数为例。我们只要维护一个栈,每枚举一个数入栈之前,都把栈里不小于它的数弹出,这样每次求一个数左边第一个小于它的数,就只需要取栈顶元素。
题目链接
参考代码:
#include
using namespace std;
int main()
{
int n; cin >> n;
stack<int> stk;
for(int i = 1; i <= n; i++)
{
int x; cin >> x;
while(!stk.empty() && stk.top() >= x) stk.pop();
if(stk.empty()) cout << -1 << ' ';
else cout << stk.top() << ' ';
stk.push(x);
}
}
常见题型:滑动窗口求最值
首先,滑动窗口可以用一个队列来维护:滑动窗口每次向右走一步,队尾就插入一个数,由于滑动窗口的长度是定值,如果此时队头不合法就要弹出一个数。
暴力的做法是,滑动窗口每走一步,都扫描一遍滑动窗口的区间求最值。显然这种做法是 O ( N 2 ) O(N^2) O(N2) 的。
如何优化呢?以求最大值为例,每枚举一个数入队之前,都把队列里不大于它的数弹出,再将这个数入队,以此来维护一段单调递减的区间。这样滑动窗口每次求最大值,就只需要取队头元素。
题目链接
参考代码:
#include
using namespace std;
const int N = 1e6 + 10;
int a[N];
int main()
{
int n, k; cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i];
deque<int> dq;//队列存下标,才能判断队头合法性
//滑动窗口最小值
for(int i = 1; i <= n; i++)
{
//判断队头合法性: 右端为i,长度为k的区间:[i - k + 1, i]
if(!dq.empty() && dq.front() < i - k + 1) dq.pop_front();
//维护队列单调递增
while(!dq.empty() && a[dq.back()] >= a[i]) dq.pop_back();
dq.push_back(i);
//滑动窗口最小值取队头即可
if(i >= k) cout << a[dq.front()] << ' ';
}
cout << '\n';
//清空队列
dq.clear();
//滑动窗口最大值
for(int i = 1; i <= n; i++)
{
//判断队头合法性: 右端为i,长度为k的区间:[i - k + 1, i]
if(!dq.empty() && dq.front() < i - k + 1) dq.pop_front();
//维护队列单调递减
while(!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();
dq.push_back(i);
//滑动窗口最大值取队头即可
if(i >= k) cout << a[dq.front()] << ' ';
}
}
题目链接
参考代码:
#include
using namespace std;
const int N = 1e5 + 10;
int p[N];
//p[x]是x的父亲
//p[x]==x表示x是根
//返回根
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);//路径压缩
return p[x];
}
int main()
{
int n, m; cin >> n >> m;
//初始化
for(int i = 1; i <= n; i++) p[i] = i;
while(m--)
{
string s;
int a, b;
cin >> s >> a >> b;
if(s == "M") p[find(a)] = find(b);//合并
else
{
if(find(a) == find(b))//查询
cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
}
}