基础算法
二分
左边界
int l = 0, r = n - 1;
while (l < r){
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
右边界
int l = 0, r = n - 1;
while (l < r){
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
三分
double l = 0 , r = 1000 , exp = 0.00000000001;
while (r - l > exp){
double mid1 = l+(r-l) / 3; // 取 1/3
double mid2 = r-(r-l) / 3; // 取 2/3
if (check(mid1) > check(mid2)) l = mid1;
else
r = mid2;
}
注: exp 误差进度取题目要求答案后两位。例如向后取4位, 只需要向后取6位。
排序
快速排序
struct Range {
int start, end;
Range(int s = 0, int e = 0) { start = s, end = e; }
};
template <typename T>
void quick_sort(T arr[], const int len) {
if (len <= 0) return;
Range r[len];
int p = 0;
r[p++] = Range(0, len - 1);
while (p) {
Range range = r[--p];
if (range.start >= range.end) continue;
T mid = arr[range.end];
int left = range.start, right = range.end - 1;
while (left < right) {
while (arr[left] < mid && left < right) left++;
while (arr[right] >= mid && left < right) right--;
std::swap(arr[left], arr[right]);
}
if (arr[left] >= arr[range.end])
std::swap(arr[left], arr[range.end]);
else
left++;
r[p++] = Range(range.start, left - 1);
r[p++] = Range(left + 1, range.end);
}
}
快选算法
int quick_sort(int l , int r , int k){
if(l == r) return q[l];
int i = l - 1 , j = r + 1 , x = q[l + r >> 1];
while(i < j){
while(q[++i] < x);
while(q[--j] > x);
if(i < j) swap(q[i] , q[j]);
}
int sl = j - l + 1;
if(k <= sl) return quick_sort(l , j , k);
return quick_sort(j + 1 , r , k - sl);
}
归并排序
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
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 tmp[k ++ ] = q[j ++ ]; //res += mid - i + 1; 逆序对数量
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];
}
二维前缀和
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];
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
二维差分
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 ++ )
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
insert 函数
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;
}
双指针
最大不连续子段
for(int i=1 , j = 1; i <= n; i++){
s[a[i]] ++;
while (s[a[i]] > 1)
{
s[a[j]] --; // 删掉前面重复的数
j ++;
}
res = max(res , i - j + 1);
}
离散化
- 排序 , 使用sort 函数
- 去重 , 使用unique 函数
- 二分索引 , 推荐可以使用lower_bound , 不需要写二分函数。 [左闭右开]
高精度
加法
不压位
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
压9位
#include <iostream>
#include <vector>
using namespace std;
const int base = 1000000000;
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % base);
t /= base;
}
if (t) C.push_back(t);
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
{
s += (a[i] - '0') * t;
j ++, t *= 10;
if (j == 9 || i == 0)
{
A.push_back(s);
s = j = 0;
t = 1;
}
}
for (int i = b.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
{
s += (b[i] - '0') * t;
j ++, t *= 10;
if (j == 9 || i == 0)
{
B.push_back(s);
s = j = 0;
t = 1;
}
}
auto C = add(A, B);
cout << C.back();
for (int i = C.size() - 2; i >= 0; i -- ) printf("%09d", C[i]);
cout << endl;
return 0;
}
减法
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A, vector<int> &B)
{
if (A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i -- )
if (A[i] != B[i])
return A[i] > B[i];
return true;
}
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
vector<int> C;
if (cmp(A, B)) C = sub(A, B); // 注意这里需要比较一下
else C = sub(B, A), cout << '-';
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
乘法
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
除法
给定两个非负整数(不含前导 00) A , B ,请你计算 A/B 的商和余数。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
B一定不为 0
vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度取模
std::string s;
std::cin >> s;
LL sum = 0;
for (int i = 0; i < int(s.size()); i ++ ){
int c = s[i] - '0';
sum = (sum * 10 % mod + c) % mod;
}
线性数据结构
单调栈
#include <iostream>
#include <stack>
using namespace std;
int n;
stack <int> st;
int main () {
cin >> n;
for (int i = 1;i <= n;i++) {
int x;
cin >> x;
while (!st.empty () && st.top () >= x) st.pop ();
if (!st.empty ()) cout << st.top () << ' ';
else cout << -1 << ' ';
st.push (x);
}
return 0;
}
单调队列
给定定长长度为k的窗口 , 输出序列中的最大(小)值
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <vector>
int n , k;
int main(){
std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);
std::cin >> n >> k;
std::vector<int> v(n);
for (int i = 0; i < n; i ++ )
std::cin >> v[i];
std::deque<int> q;
for (int i = 0; i < n; i ++ ){
if (q.size() && q.front() < i - k + 1) q.pop_front();
while (q.size() && v[q.back()] >= v[i]) q.pop_back();
q.push_back(i);
if (i - k + 1 >= 0) std::cout << v[q.front()] << ' ';
}
std::cout << '\n';
q.clear();
for (int i = 0; i < n; i ++ ){
if (q.size() && q.front() < i - k + 1) q.pop_front();
while (q.size() && v[q.back()] <= v[i] ) q.pop_back();
q.push_back(i);
if (i - k + 1 >= 0) std::cout << v[q.front()] << ' ';
}
return 0;
}
(单)链表
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int m;
int head , e[N] , ne[N] , idx;
void init(){
head = -1;
idx = 0;
}
void add_to_head(int x){
e[idx] = x , ne[idx] = head , head = idx ++;
}
void remove(int k){
ne[k] = ne[ne[k]];
}
void insert(int k , int x){
e[idx] = x , ne[idx] = ne[k] , ne[k] = idx ++;
}
int main(){
scanf("%d" , &m);
init();
while(m --){
int k , x;
char op;
cin >> op;
if(op == 'H'){
cin >> x;
add_to_head(x);
}
else if(op == 'D'){
cin >> k;
if(!k) head = ne[head];
else remove(k - 1);
}
else{
cin >> k >> x;
insert(k - 1 , x);
}
}
for (int i = head; i != -1; i = ne[i])
cout << e[i] << ' ';
return 0;
}
字符串
KMP
- 求字符串t在字符串s中出现的次数
- 所有出现的位置
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010 , M = 1000010;
int n , m;
int ne[N];
char p[N] , s[M];
int main(){
scanf("%d%s%d%s" , &n , p + 1 , &m , s + 1);
for (int i = 2 , j = 0; p[i]; i ++){
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j ++;
ne[i] = j;
}
for (int i = 1 , j = 0; s[i] ; i ++){
while(j && s[i] != p[j + 1]) j = ne[j]
if(s[i] == p[j + 1]) j ++;
if(j == n){
printf("%d " , i - n);
j = ne[j]; // 继续下一次匹配
}
}
return 0;
}
Trie 树
维护一个字符串集合,支持两种操作:
I x向集合中插入一个字符串 ;Q x询问一个字符串在集合中出现了多少次。
共有 N𝑁 个操作,所有输入的字符串总长度不超过 105105,字符串仅包含小写英文字母。
const int N = 100010 , M = 20010;
int n , idx;
int son[N][26] , cnt[N];
char str[M];
void insert(char * str){
int p = 0;
for (int i = 0; str[i] ; i++){
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p] ++;
}
int query(char str[]){
int p = 0;
for (int i = 0; str[i] ; i++){
int u = str[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
AC 自动机
给定 n 个模式串 s 和一个文本串 t,求有多少个不同的模式串在文本串里出现过。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1000100 , M = 30;
int n , idx;
char s[N];
struct node{
int son[M];
int fail , cnt;
}tr[N];
void insert(char * s){
int p = 0;
for(int i = 0; s[i] ; i ++ ){
int u = s[i] - 'a';
if(!tr[p].son[u]) tr[p].son[u] = ++idx;
p = tr[p].son[u];
}
tr[p].cnt ++;
}
void build(){
queue<int> q;
for (int i = 0; i < 26; i ++ )
if(tr[0].son[i])
q.push(tr[0].son[i]);
while(q.size()){
int u = q.front();
q.pop();
for (int i = 0; i < 26; i ++ ){
if(tr[u].son[i])
tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i] , q.push(tr[u].son[i]);
else
tr[u].son[i] = tr[tr[u].fail].son[i];
}
}
}
int query(char * s){
int p = 0 , ans = 0;
for (int i = 0; s[i] ; i ++ ){
p = tr[p].son[s[i] - 'a'];
for (int j = p; j && tr[j].cnt != -1; j = tr[j].fail)
ans += tr[j].cnt , tr[j].cnt = -1;
}
return ans;
}
int main(){
scanf("%d" , &n);
for (int i = 0; i < n; i ++ ){
scanf("%s" , s);
insert(s);
}
build();
scanf("%s" , s);
int t = query(s);
printf("%d" , t);
return 0;
}
Manacher
求最长回文子串
#include <iostream>
#include <cstring>
using namespace std;
const int N = 11000010;
char a[N] , s[N * 2];
int d[N * 2];
int manacher(){
int k = 0 , n = strlen(a + 1);
s[0] = '$' , s[++k] = '#';
for (int i = 1; i <= n; i ++ )
s[++k] = a[i] , s[++k] = '#';
n = k;
int ans = 1;
d[1] = 1;
for (int i = 2 , l , r = 1; i <= n; i ++ ){
if(i <= r) d[i] = min(d[r - i + l] , r - i + 1);
while(s[i - d[i]] == s[i + d[i]]) d[i] ++;
if(i + d[i] - 1 > r)
l = i - d[i] + 1 , r = i + d[i] - 1 , ans = max(ans , (r - l + 1) / 2);
}
return ans;
}
int main(){
scanf("%s" , a + 1);
int t = manacher();
printf("%d" , t);
return 0;
}
树形数据结构
线段树
区间加乘
#include <iostream>
#include <cstring>
#include <algorithm>
#define ls u << 1
#define rs u << 1 | 1
#define LL long long
const int N = 100010;
int n , m , p;
int a[N];
struct node{
int l , r;
LL sum , mul , add;
}tr[4 * N];
void pushup(int u){
tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;
}
void cacl(node &T , LL m , LL a){
T.sum = ((T.sum * m) % p + (T.r - T.l + 1) * a) % p;
T.mul = T.mul * m % p;
T.add = (T.add * m % p + a) % p;
}
void pushdown(int u){
cacl(tr[ls] , tr[u].mul , tr[u].add);
cacl(tr[rs] , tr[u].mul , tr[u].add);
tr[u].mul = 1 , tr[u].add = 0; // 清除标记
}
void build(int u , int l , int r){
tr[u] = {l , r , a[l] , 1 , 0};
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls , l , mid);
build(rs , mid + 1 , r);
pushup(u);
}
void modify(int u , int l , int r , LL m , LL a){
if(tr[u].r < l || tr[u].l > r) return ;
if(l <= tr[u].l && tr[u].r <= r){
cacl(tr[u] , m , a);
return ;
}
pushdown(u);
modify(ls , l , r , m , a);
modify(rs , l , r , m , a);
pushup(u);
}
LL query(int u , int l , int r){
if(tr[u].r < l || tr[u].l > r) return 0;
if(l <= tr[u].l && tr[u].r <= r)
return tr[u].sum % p;
LL sum = 0;
pushdown(u);
sum += query(ls , l , r);
sum += query(rs , l , r);
pushup(u);
return sum % p;
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m >> p;
for (int i = 1; i <= n; i ++ ) std::cin >> a[i];
build(1 , 1 , n);
while(m -- ){
int opt , l , r , k;
std::cin >> opt >> l >> r;
if(opt != 3){
std::cin >> k;
if(opt == 1) modify(1 , l , r , k , 0); // 区间乘法
else modify(1 , l , r , 1 , k); // 区间加法
}
else
std::cout << query(1 , l , r ) << '\n';
}
return 0;
}
区间最大子段和
#include <iostream>
#include <cstring>
#include <algorithm>
#define ls u << 1
#define rs u << 1 | 1
#define LL long long
const int N = 500010;
const LL INF = 1E18;
int n , m;
int a[N];
struct node{
int l , r;
LL sum , lmx , rmx , mx;
}tr[4 * N];
void pushup(node &T , const node &L , const node &R){
T.sum = L.sum + R.sum;
T.mx = std::max(std::max(L.mx , R.mx), L.rmx + R.lmx);
T.lmx = std::max(L.lmx , L.sum + R.lmx);
T.rmx = std::max(R.rmx , R.sum + L.rmx);
}
void build(int u , int l , int r){
tr[u] = {l , r , a[l] , a[l] , a[l] , a[l]};
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls , l , mid);
build(rs , mid + 1 , r);
pushup(tr[u] , tr[ls] , tr[rs]);
}
void modify(int u , int x , LL c){
if(tr[u].l == x && tr[u].r == x) {
tr[u] = {x , x , c , c , c , c};
return ;
}
int mid = (tr[u].l + tr[u].r ) >> 1;
if(x <= mid) modify(ls , x , c);
else
modify(rs , x, c);
pushup(tr[u] , tr[ls] , tr[rs]);
}
node query(int u , int l , int r ){
if(l <= tr[u].l && tr[u].r <= r) return tr[u];
int mid = (tr[u].l + tr[u].r) >> 1;
if(r <= mid) return query(ls , l , r);
if(l > mid) return query(rs , l , r);
node T ;
pushup(T , query(ls , l , mid) , query(rs , mid + 1 , r) );
return T;
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; i ++ ) std::cin >> a[i];
build(1 , 1 , n);
while(m -- ){
int opt , l , r;
std::cin >> opt >> l >> r;
if(opt == 1){
if(l > r) std::swap(l , r);
std::cout << query(1 , l , r).mx << '\n';
}else
modify(1 , l , r);
}
return 0;
}
树状数组
- 将某区间每一个数加上 ;
- 求出某一个数的值。
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
const int N = 500010;
int n , m;
LL tr[N];
inline int lowbit(int x){
return x & (-x);
}
void modify(int u , LL k){
while(u <= n)
tr[u] += k , u = u + lowbit(u);
}
LL query(int x){ // 求前 x项的和
LL sum = 0;
while(x)
sum += tr[x] , x -= lowbit(x);
return sum;
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
LL x , t = 0;
for (int i = 1; i <= n; i ++ ){
std::cin >> x;
modify(i , x - t);
t = x;
}
while(m -- ){
int opt , x , y , k;
std::cin >> opt >> x;
if(opt == 1){
std::cin >> y >> k;
modify(x , k);
modify(y + 1 , -k);
}
else
std::cout << query(x) << '\n';
}
return 0;
}
二维区间加区间和
typedef long long ll;
ll t1[N][N], t2[N][N], t3[N][N], t4[N][N];
void add(ll x, ll y, ll z) {
for (int X = x; X <= n; X += lowbit(X))
for (int Y = y; Y <= m; Y += lowbit(Y)) {
t1[X][Y] += z;
t2[X][Y] += z * x; // 注意是 z * x 而不是 z * X,后面同理
t3[X][Y] += z * y;
t4[X][Y] += z * x * y;
}
}
void range_add(ll xa, ll ya, ll xb, ll yb,
ll z) { //(xa, ya) 到 (xb, yb) 子矩阵
add(xa, ya, z);
add(xa, yb + 1, -z);
add(xb + 1, ya, -z);
add(xb + 1, yb + 1, z);
}
ll ask(ll x, ll y) {
ll res = 0;
for (int i = x; i; i -= lowbit(i))
for (int j = y; j; j -= lowbit(j))
res += (x + 1) * (y + 1) * t1[i][j] - (y + 1) * t2[i][j] -
(x + 1) * t3[i][j] + t4[i][j];
return res;
}
ll range_ask(ll xa, ll ya, ll xb, ll yb) {
return ask(xb, yb) - ask(xb, ya - 1) - ask(xa - 1, yb) + ask(xa - 1, ya - 1);
}
区间查询和修改的时间复杂度都是
区间gcd
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ls u << 1
#define rs u << 1 | 1
#define LL long long
const int N = 500010;
int n , m;
LL a[N];
struct Node{
int l , r;
LL sum , d;
}tr[4 * N];
LL gcd(LL a , LL b){
return b ? gcd(b , a % b) : a;
}
void pushup(Node &u , const Node &L , const Node &R){
u.sum = L.sum + R.sum;
u.d = gcd(L.d , R.d);
}
void build(int u , int l , int r ){
tr[u] = {l , r};
if (l == r) {
LL b = a[l] - a[l - 1];
tr[u].sum = b;
tr[u].d = b;
return ;
}
int mid = (l + r) >> 1;
build(ls , l , mid);
build(rs , mid + 1 , r);
pushup(tr[u] , tr[ls] , tr[rs]);
}
void modify(int u , int x , LL c){
if(tr[u].l == x && tr[u].r == x) {
LL t = tr[u].sum + c;
tr[u] = {x , x , t , t};
return ;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) modify(ls , x , c);
else
modify(rs , x , c);
pushup(tr[u] , tr[ls] , tr[rs]);
}
Node query(int u , int l , int r){
if (l <= tr[u].l && tr[u].r <= r) return tr[u];
int mid = (tr[u].l + tr[u].r) >> 1;
if (r <= mid) return query(ls , l , r);
if (l > mid) return query(rs , l , r);
Node T;
pushup(T , query(ls , l , mid) , query(rs , mid + 1 , r));
return T;
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; i ++ ) std::cin >> a[i];
build(1 , 1 , n);
while(m -- ){
std::string opt;
int l , r;
LL d;
std::cin >> opt >> l >> r ;
if(opt == "C"){
std::cin >> d;
modify(1 , l , d);
if(r + 1 <= n) modify(1 , r + 1, -d); // 区间转单点修改
}
else{
Node L = query(1 , 1 , l); // [1 , L]
Node R({0 , 0 , 0 , 0});
if(l + 1 <= r) R = query(1 , l + 1 , r); // [L + 1 , R]
std::cout << std::abs(gcd(L.sum , R.d)) << '\n';
}
}
return 0;
}
splay
#include <iostream>
using namespace std;
const int N = 100010 , INF = 0x3f3f3f3f;
struct node {
int p , s[2]; // 父节点 , 左右孩子
int val; // 值
int size , cnt;
void init(int v , int fa){
size = cnt = 1;
p = fa , val = v;
}
}tr[N];
int root , idx;
int n;
/**
* @param x 代表当前子树的根节点
* 统计这颗子树的大小 size
*/
void pushup(int x){
tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + tr[x].cnt;
}
/**
* @param x 传入需要旋转的结点
* 假设 x 是左儿子 , 那么右旋
* 假设 x 是右儿子 , 那么左旋
*
* @variety
* \t y 为初始旋转的根\n
* \t z 为 y为根的这颗子树的根节点\n
* \t k 代表子树的方向 0 代表左子树 , 1 代表右子树\n
* \t k ^ 1 代表与 x 子树方向 相反方向的子树 \n 例如 k == 1 代表右子树 , 那么 k ^ 1 指代的左子树
*/
void rotate(int x) {
int y = tr[x].p , z = tr[y].p , k = tr[y].s[1] == x;
// x相反方向的子树b , 成为y的与x方向相同方向子树. 即 b 和 y 建立连接
tr[tr[x].s[k ^ 1]].p = y;
tr[y].s[k] = tr[x].s[k ^ 1];
// x 与 y 建立连接
tr[y].p = x;
tr[x].s[k ^ 1] = y;
// x 与 z 建立连接
tr[z].s[tr[z].s[1] == y] = x;
tr[x].p = z;
// 旋转之后需要 pushup , 因为子树发生改变 , 需要重新统计size
pushup(y) , pushup(x); // 注意受影响的子树 , 自底向上统计 y ---> x
}
/**
*
* @param x 代表 x 需要旋转的点
* @param k 代表需要旋转到下面的点
*/
void splay(int x , int k){
while(tr[x].p != k){ // 旋转到 k 下面就停止
int y = tr[x].p , z = tr[y].p;
if(z != k) // 说明需要双旋 折转底 , 直转中
(tr[y].s[0] == x) ^ (tr[z].s[0] == y) ? rotate(x) : rotate(y);
rotate(x);
}
if(k == 0) root = x; // 把x旋转到根 , 那么更新根
}
/**
* @param val 需要插入的值
*/
void insert(int val){
int x = root , p = 0;
while(x && tr[x].val != val)
p = x , x = tr[x].s[val > tr[x].val];
if(x)
tr[x].cnt ++;
else{
x = ++ idx; // 建结点
tr[p].s[val > tr[p].val] = x; // 父节点指向子节点
tr[x].init(val , p);
}
splay(x , 0); // 重新统计树的 size
}
/**
* @param val 待查找的值 , 将该旋转到根结点
*/
void find(int val){
int x = root;
while(tr[x].s[val > tr[x].val] && val != tr[x].val)
x = tr[x].s[val > tr[x].val]; // 向下找
splay(x , 0); // 1、降低树高 2、好获取答案
}
/**
* @param val 待查找前驱的值
* @return 返回前驱的下标
*/
int get_pre(int val){
find(val);
int x = root;
if(tr[x].val < val) return x;
x = tr[x].s[0]; // 从左子树开始
while(tr[x].s[1]) // 一直向右走
x = tr[x].s[1];
splay(x , 0); // 调整一下树高
return x;
}
/**
* @param val 待查找后继的点
* @return 返回后继的下标
*/
int get_suc(int val){
find(val);
int x = root;
if(tr[x].val > val) return x;
x = tr[x].s[1]; // 从右子树开始
while(tr[x].s[0]) // 一直向左边走
x = tr[x].s[0];
splay(x , 0);
return x;
}
/**
* @param val 待删除的结点
*/
void del(int val){
int pre = get_pre(val);
int suc = get_suc(val);
splay(pre , 0 ) , splay(suc , pre);
int del = tr[suc].s[0]; // val 结点
// splay 因为删除一个结点 , 所以需要splay 重新统计size 信息
if(tr[del].cnt > 1)
tr[del].cnt -- , splay(del , 0 );
else
tr[suc].s[0] = 0 , splay(suc , 0);
}
/**
* @param val 待查找排名的值
* @return 返回该值的排名
*/
int get_rank(int val){ // 获取排名
insert(val);
int res = tr[tr[root].s[0]].size;
del(val);
return res; // 因为还有一个哨兵 , 所以不需要加一
}
/**
* @param k 查找排名为k的下标
* @return 返回查找到的值
*/
int get_val(int k){ // 根据排名找值
int x = root;
while(1){
int y = tr[x].s[0];
if(tr[y].size + tr[x].cnt < k){ // 说明在右子树
k -= tr[y].size + tr[x].cnt;
x = tr[x].s[1];
}else
if(tr[y].size >= k) x = tr[x].s[0];
else
break;
}
splay(x , 0);
return tr[x].val;
}
int main(){
insert(-INF) , insert(INF);
scanf("%d" , &n);
int op , x;
while(n -- ){
scanf("%d%d" , &op , &x);
if(op == 1) insert(x);
else if(op == 2) del(x);
else if(op == 3) printf("%d\n" , get_rank(x));
else if(op == 4) printf("%d\n" , get_val(x + 1)); // 注意排名 + 1
else if(op == 5) printf("%d\n" , tr[get_pre(x)].val);
else printf("%d\n" , tr[get_suc(x)].val);
}
return 0;
}
文艺平衡树
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1,翻转区间是 [2,4] 的话,结果是 5 2 3 4 1。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010 , INF = 0x3f3f3f3f;
int n , m;
struct node{
int p , s[2];
int val;
int size , tag;
void init(int val , int p){
this->val = val;
this->p = p;
this->size = 1;
}
}tr[N];
int root , idx;
void pushup(int x){
tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;
}
void rotate(int x ){
int y = tr[x].p , z = tr[y].p , k = tr[y].s[1] == x;
// b 与 y 建边
tr[tr[x].s[k ^ 1]].p = y;
tr[y].s[k] = tr[x].s[k ^ 1];
// x 与 y 建边
tr[x].s[k ^ 1] = y;
tr[y].p = x;
// x 与 z 建边
tr[z].s[tr[z].s[1] == y] = x;
tr[x].p = z;
pushup(y) , pushup(x);
}
void splay(int x , int k){ // 将 x 转成 k 的儿子
while(tr[x].p != k){
int y = tr[x].p , z = tr[y].p;
if(z != k)
(tr[y].s[1] == x) ^ (tr[z].s[1] == y) ? rotate(x) : rotate(y); // 直转折 , 折转底
rotate(x);
}
if(k == 0) root = x;
}
void pushdown(int x){
if(tr[x].tag){
swap(tr[x].s[0] , tr[x].s[1]); // 将两棵子树翻转
tr[tr[x].s[0]].tag ^= 1;
tr[tr[x].s[1]].tag ^= 1;
tr[x].tag ^= 1;
}
}
void insert(int val){
int x = root , p = 0;
while(x) p = x , x = tr[x].s[val > tr[x].val];
x = ++idx;
tr[p].s[val > tr[p].val] = x; // 父节点 指向 子节点
tr[x].init(val , p);
splay(x , 0);
}
int get_k(int k){ // 取得第k大的数 的下标
int x = root;
while(1){
pushdown(x);
int y = tr[x].s[0];
if(tr[y].size + 1 < k) k -= (tr[y].size + 1) , x = tr[x].s[1];
else if(tr[y].size >= k) x = y;
else
return x;
}
}
void output(int x){
if(x == 0) return ;
pushdown(x);
output(tr[x].s[0]);
if(1 <= tr[x].val && tr[x].val <= n) // 过滤到-INF , INF
printf("%d " , tr[x].val);
output(tr[x].s[1]);
}
int main(){
scanf("%d%d" , &n , &m);
insert(-INF) , insert(INF);
for (int i = 1; i <= n; i ++ ) insert(i);
while(m -- ){
int l , r;
scanf("%d%d" , &l , &r);
l = get_k(l) , r = get_k(r + 2); // 夹挤区间 [l , r]
splay(l , 0) , splay(r , l);
tr[tr[r].s[0]].tag ^= 1; // r 打上标记
}
output(root);
return 0;
}
并查集
集合的合并、查询
权值并查集
参考题星球大战
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
const int N = 500010;
int m;
int p[N] , size[N] , d[N];
int find(int x){
if (p[x] != x){
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> m;
for (int i = 1; i <= N; i ++ ) {
p[i] = i;
size[i] = 1;
}
while(m -- ){
std::string opt;
int a , b;
std::cin >> opt >> a >> b;
int pa = find(a) , pb = find(b);
if (opt == "M"){
if (pa != pb){
p[pa] = pb;
d[pa] += size[pb];
size[pb] += size[pa];
}
}
if (opt == "C"){
if(pa != pb) std::cout << -1 << '\n';
else
std::cout << std::max(0 , std::abs(d[a] - d[b]) -1 ) << '\n';
}
}
return 0;
}
串hash unordered_map
常用操作介绍
主要介绍curd,以及其他好用的函数。
插入元素
// 插入方法 1
un_mp.insert(std::unordered_map<std::string , int>::value_type("test" , 1));
// 插入方法 2
un_mp["test"] = 333;
std::cout << un_mp["test"] << '\n';
明显我们发现第二种插入方法更加的方便 un_map[key] = value
删除元素
un_mp["test"] = 333;
un_mp.erase("test");
std::cout << un_mp["test"] << '\n';
注: 如果删除的key 不存在的话 ,那么erase 函数会返回 0 , 其中返回值表示的是删除元素的个数。
查询元素
auto f = un_mp.find("test");
if (f != un_mp.end())
std::cout << f -> second << '\n';
返回的是迭代器的指针 , 如果不存在的话返回 un_map.end()
修改元素
这里推荐的指正修改和 [] 运算符直接修改
- 第一个指针修改
auto f = un_mp.find("test"); std::cout << f -> second << '\n'; - 第二个 [] 运算符修改
un_mp["test"] = 333; 通过key修改value
遍历集合
un_mp["s"] = 1;
un_mp["ss"] = 2;
un_mp["sss"] = 3;
un_mp["ssss"] = 4;
for (auto x : un_mp)
std::cout << x.second << ' ';
防被卡hash
很多比赛都会针对 和 的底层hash值 , 来造数据 ,导致对于单次的查询为 , 而不是 的 , 所以可能对于n个元素的插入,再查询可能是 , 导致超时。 所以给出一种防被卡的方法
struct custom_hash {
static LL splitmix64(LL x) {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
return x;
}
size_t operator () (LL x) const {
static const LL FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count(); // 时间戳
return splitmix64(x + FIXED_RANDOM);
}
};
可以这么定义
std::unordered_map<LL, bool, custom_hash> safe_map;
注意: 对于hash函数如果是的话 , 只需要将字符串hash成数值,再进行以上操作 , 修改 重载的 () 的LL---->std::string。
补充
插入元素(不支持 [] 运算符)
mp.insert(std::make_pair(2 , 1));
删除键对应一个值
// 匹配键的值
mp.erase(2);
删除指定键的元素
auto it = mp.find(2); // 删除迭代器指向的元素
if (it != mp.end()) {
mp.erase(it);
}
图论
单源最短路 Dijkstra (顶点1为起点)
std::vector<PII> e[N];
void dijkstra(int s){
std::vector<int> dist(n + 1 , INF);
std::vector<bool> st(n + 1 , 0);
for (int i = 1; i <= p; i ++ )
dist[i] = INF , st[i] = 0;
dist[s] = 0;
std::priority_queue<PII , std::vector<PII> , std::greater<PII>> heap;
heap.push({0 , s}); // 加入始点
while (heap.size()){
auto t = heap.top();
heap.pop();
int u = t.second;
if (st[u]) continue;
st[u] = true;
for (auto p : e[u]){
int v = p.first , w = p.second;
if (dist[v] > dist[u] + w){
dist[v] = dist[u] + w;
heap.push({dist[v] , v});
}
}
}
}
单源k短路(最多经过k条边)bellman-ford
起点为 1
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010;
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++ )
{
memcpy(last, dist, sizeof dist);
for (int j = 0; j < m; j ++ )
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n", dist[n]);
return 0;
}
spfa 算法
struct edge {
int v, w;
};
vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;
bool spfa(int n, int s) {
memset(dis, 63, sizeof(dis));
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1; // 记录最短路经过的边数
if (cnt[v] >= n) return false;
// 在不经过负环的情况下,最短路至多经过 n - 1 条边
// 因此如果经过了多于 n 条边,一定说明经过了负环
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return true;
}
spfa 判负环
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
const int N = 510 , INF = 0x3f3f3f3f;
#define PII std::pair<int , int>
int n , m , w;
std::vector<PII> e[N];
bool spfa(){
std::vector<bool> st(n + 1);
std::vector<int> num(n + 1);
std::vector<int> dist(n + 1);
std::queue<int> q;
for (int i = 1; i <= n; i ++ ){
q.push(i);
st[i] = 1;
}
while (q.size()){
int u = q.front();
q.pop();
st[u] = 0;
for (auto p : e[u]){
int v = p.first , w = p.second;
if (dist[v] > dist[u] + w){
dist[v] = dist[u] + w;
num[v] = num[u] + 1;
if (num[v] >= n) return true;
if (!st[v]){
q.push(v);
st[v] = 1;
}
}
}
}
return false;
}
void solved(){
for (int i = 1; i <= n; i ++ ) e[i].clear();
std::cin >> n >> m >> w;
for (int i = 0; i < m; i ++ ){
int a , b , c;
std::cin >> a >> b >> c;
e[a].push_back({b , c});
e[b].push_back({a , c});
}
for (int i = 0; i < w; i ++ ){
int a , b , c;
std::cin >> a >> b >> c;
e[a].push_back({b , -c});
}
bool ok = spfa();
std::cout << (ok ? "YES" : "NO") << '\n';
}
int main(){
int tc = 1;
std::cin >> tc;
while (tc --)
solved();
return 0;
}
多源最短路 Floyd
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
int t = d[a][b];
if (t > INF / 2) puts("impossible"); // 注意这个判无法联通 INF / 2
else printf("%d\n", t);
}
return 0;
}
全源最短路 johnson 算法
给定一个包含 个结点和 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。
#include <cstring>
#include <iostream>
#include <queue>
#define INF 1e9
using namespace std;
struct edge {
int v, w, next;
} e[10005];
struct node {
int dis, id;
bool operator<(const node& a) const { return dis > a.dis; }
node(int d, int x) { dis = d, id = x; }
};
int head[5005], vis[5005], t[5005];
int cnt, n, m;
long long h[5005], dis[5005];
void addedge(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
bool spfa(int s) {
queue<int> q;
memset(h, 63, sizeof(h));
h[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (h[v] > h[u] + e[i].w) {
h[v] = h[u] + e[i].w;
if (!vis[v]) {
vis[v] = 1;
q.push(v);
t[v]++;
if (t[v] == n + 1) return false;
}
}
}
}
return true;
}
void dijkstra(int s) {
priority_queue<node> q;
for (int i = 1; i <= n; i++) dis[i] = INF;
memset(vis, 0, sizeof(vis));
dis[s] = 0;
q.push(node(0, s));
while (!q.empty()) {
int u = q.top().id;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
if (!vis[v]) q.push(node(dis[v], v));
}
}
}
return;
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
addedge(u, v, w);
}
for (int i = 1; i <= n; i++) addedge(0, i, 0);
if (!spfa(0)) {
cout << -1 << endl;
return 0;
}
for (int u = 1; u <= n; u++)
for (int i = head[u]; i; i = e[i].next) e[i].w += h[u] - h[e[i].v];
for (int i = 1; i <= n; i++) {
dijkstra(i);
long long ans = 0;
for (int j = 1; j <= n; j++) {
if (dis[j] == INF)
ans += j * INF;
else
ans += j * (dis[j] + h[j] - h[i]);
}
cout << ans << endl;
}
return 0;
}
开一个 pre 数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。
比如 Floyd 就要记录 pre[i][j] = k;,Bellman–Ford 和 Dijkstra 一般记录 pre[v] = u。
Prime 算法求最小生成树
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
Kruskal算法求最小生成树
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge
{
int a, b, w;
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[M];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m);
for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集
int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res += w;
cnt ++ ;
}
}
if (cnt < n - 1) return INF;
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
强连通分量 tarjan
强连通分量、缩点 参考例题 P2812 校园网络
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#define LL long long
#define PII std::pair<int , int>
const int N = 100010;
std::stack<int> stk;
std::vector<int> e[N];
int n , m , times , scc_cnt;
int id[N] , size[N];
int dfn[N] , low[N];
bool vis[N] , in_stack[N];
int in[N] , out[N];
void tarjan(int u ){
// 入 盖戳
dfn[u] = low[u] = ++ times;
vis[u] = true;
stk.push(u) , in_stack[u] = true; // 加入栈中
for (int v : e[u])
if (!vis[v]){
tarjan(v); // 下
low[u] = std::min(low[u] , low[v]); // 回 , 子节点所能到的最低结点 , 父节点一定能到
}else if (in_stack[v])
low[u] = std::min(low[u] , dfn[v]); // 触 当前点再次搜索到 , 那么说明 v 是一个回溯点
// 离 当前点是回溯点
if (low[u] == dfn[u]){
int y; ++ scc_cnt;
do{
y = stk.top();
stk.pop();
in_stack[y] = false;
id[y] = scc_cnt;
size[scc_cnt] ++;
}while (y != u);
}
}
int main(){
std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);
std::cin >> n;
for (int i = 1; i <= n; i ++ ){
int x ;std::cin >> x;
while (x != 0) {
e[i].push_back(x);
std::cin >> x;
}
}
for (int i = 1; i <= n; i ++ )
if (!vis[i])
tarjan(i);
for (int i = 1; i <= n; i ++ )
for (int v : e[i])
if (id[i] != id[v]) // 不在同一个强连通分量中
in[id[v]] ++ , out[id[i]] ++;
int res_1 = 0 , res_2 = 0;
for (int i = 1; i <= scc_cnt; i ++ ){
if (in[i] == 0) res_1 ++ ;
if (out[i] == 0) res_2 ++ ;
}
std::cout << res_1 << '\n';
if (scc_cnt == 1)
std::cout << 0 << '\n';
else
std::cout << std::max(res_1 , res_2) << '\n';
return 0;
}
树链剖分
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define LL long long
const int N = 500010;
int n , m , s;
std::vector<int> e[N];
int size[N] , p[N] , dep[N] , son[N];
int top[N];
void dfs1 (int u , int fa){
p[u] = fa , size[u] = 1 , dep[u] = dep[fa] + 1;
for (auto v : e[u]){
if (v == fa) continue;
dfs1(v , u);
size[u] += size[v];
if (size[son[u]] < size[v]) son[u] = v;
}
}
void dfs2 (int u , int t){
top[u] = t;
if (son[u] == 0) return ;
dfs2(son[u] , t);
for (int v : e[u]){
if (v == p[u] || v == son[u]) continue;
dfs2(v , v);
}
}
int lca(int u , int v){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]]) std::swap(u , v);
u = p[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m >> s;
for (int i = 1; i < n; i ++ ){
int a , b;
std::cin >> a >> b;
e[a].push_back(b) , e[b].push_back(a);
}
dfs1(s , 0);
dfs2(s , 0);
while(m -- ){
int u , v;
std::cin >> u >> v;
std::cout << lca(u , v) << '\n';
}
return 0;
}
tarjan 离线算法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define LL long long
#define PII std::pair<int , int>
const int N = 500010;
int n , m , s;
std::vector<PII> q[N];
std::vector<int> e[N];
int fa[N] , ans[N];
bool vis[N];
int find (int u){
if (fa[u] != u) fa[u] = find(fa[u]);
return fa[u];
}
void tarjan (int u){
vis[u] = true;
for (int v : e[u])
if (!vis[v]){
tarjan(v);
fa[v] = u;
}
for (auto p : q[u]){
int x = p.first , i = p.second;
if (vis[x]) ans[i] = find(x);
}
}
int main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m >> s;
for (int i = 1; i <= n; i ++ ) fa[i] = i;
for (int i = 1; i < n; i ++ ){
int a , b;
std::cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
for (int i = 1; i <= m; i ++ ){
int a , b;
std::cin >> a >> b;
q[b].push_back({a , i});
q[a].push_back({b , i});
}
tarjan(s);
for (int i = 1; i <= m; i ++ )
std::cout << ans[i] << '\n';
return 0;
}
二分图染色法
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 100010;
int n, m;
std::vector<int> e[N];
int color[N];
bool dfs(int u, int c)
{
color[u] = c;
for (int v : e[u])
{
if (!color[v])
{
if (!dfs(v , 3 - c)) return false;
}
else if (color[v] == c) return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
e[a].push_back(b);
e[b].push_back(a);
}
bool flag = true;
for (int i = 1; i <= n; i ++ )
if (!color[i])
{
if (!dfs(i, 1))
{
flag = false;
break;
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}
二分图最大匹配
本质:寻找增广路
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
const int N = 510 , INF = 2e9 + 7;
#define LL long long
#define PII std::pair<int , int>
int n , m , k;
int match[N];
bool vis[N];
std::vector<int> e[N];
bool dfs(int u){ // 枚举到这个点
for (int v : e[u]){ // 枚举邻接点
if (vis[v]) continue;
vis[v] = 1;
if (!match[v] || dfs(match[v])) {
match[v] = u;
return 1;
}
}
return 0;
}
int main(){
std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);
std::cin >> n >> m >> k;
for (int i = 0; i < k; i ++ ){
int a , b;
std::cin >> a >> b;
e[a].push_back(b);
}
int ans = 0;
for (int i = 1; i <= n; i ++ ){
for (int i = 1; i <= m; i ++ ) vis[i] = 0;
if (dfs(i)) ans ++ ;
}
std::cout << ans << '\n';
return 0;
}
拓扑排序
- top 排序只需要记录入度 宽搜
- 将入度为0的结点入队
- 从入度为0的结点开始,然后擦掉它的出边
- 将入度为的0点加入队列 ,输出该点
应用1. top 排序可用于判环,如果没有走完所有点说明有环。
应用2. top 排序可用于判唯一的关系,如果队列中出现两个及以上的元素个数说明top排序不唯一。
数论
筛法
埃式筛法
#include <iostream>
#include <algorithm>
using namespace std;
const int N= 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
线性筛法
#include <iostream>
#include <algorithm>
using namespace std;
const int N= 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
约数和
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110, mod = 1e9 + 7;
int main()
{
int n;
cin >> n;
unordered_map<int, int> primes;
while (n -- )
{
int x;
cin >> x;
for (int i = 2; i <= x / i; i ++ )
while (x % i == 0)
{
x /= i;
primes[i] ++ ;
}
if (x > 1) primes[x] ++ ;
}
LL res = 1;
for (auto p : primes)
{
LL a = p.first, b = p.second;
LL t = 1;
while (b -- ) t = (t * a + 1) % mod;
res = res * t % mod;
}
cout << res << endl;
return 0;
}
快速幂
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, p;
scanf("%d%d%d", &a, &b, &p);
printf("%lld\n", qmi(a, b, p));
}
return 0;
}
快速幂求逆元
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, p;
scanf("%d%d", &a, &p);
if (a % p == 0) puts("impossible");
else printf("%lld\n", qmi(a, p - 2, p));
}
return 0;
}
高精度快速幂
快速幂
const int N = 1100;
int p;
int a[N] , res[N] , c[N];
void result_1(){
memset(c , 0 , sizeof c);
for (int i = 1; i <= 500; i ++ )
for (int j = 1; j <= 500; j ++ ) c[i + j - 1] += res[j] * a[i];
for (int i = 1; i <= 500; i ++ )
if(c[i] / 10){
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
memcpy(res , c , sizeof c);
}
void result_2(){
memset(c , 0 , sizeof c);
for (int i = 1; i <= 500; i ++ )
for (int j = 1; j <= 500; j ++ ) c[i + j - 1] += a[i] * a[j];
for (int i = 1; i <= 500; i ++ )
if(c[i] / 10){
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
memcpy(a , c, sizeof c);
}
void qmi(int p , int k){
a[1] = k , res[1] = 1;
while (p){
if(p & 1) result_1();
p >>= 1;
result_2();
}
}