并查集
int find(int x){
return f[x] == x? x : f[x] = find(f[x]);
}
void hb(int a,int b){
int x = find(a);
int y = find(b);
if(x == y)return;
f[x] = y;
}快速幂
int qpow(int a, int b){
int res = 1;
while(b){
if(b & 1)res = (res * a) % mod;
a = a * a % mod;
b >>= 1;
}
return res % mod;
}最大公约数 gcd
int gcd(int a, int b){
if(b == 0) return a;
retrun gcd(b, a % b);
}最小公约数 lcm
int lcm(int a, int b){
return a * b / gcd(a, b);
}迪杰斯特拉求最短路径
时间复杂度 :
空间复杂度 :
int n, m, s;
void solve(){
cin>>n>>m>>s;
vector<pair<int,int>> G[n+10];
rep(i,1,m){
int a,b,c;cin>>a>>b>>c;
G[a].push_back({b,c});
}
vi dis(n + 10, INF); //记录 1 到这个 点 的最短距离
vi vis(n + 10); //记录这个点是否出圈,也就是是否已经到 到这个点 的最短路径
dis[s] = 0;
priority_queue <pair<int, int>> q; //优先队列,现存的最短路
q.push({0, s});
while(q.size()){
auto x = q.top();
q.pop();
int a = -x.first;
int b = x.second;
if(vis[b])continue;//到达这个点的最短的最短路径已经求了 //这个点已经不用再去找了
vis[b] = 1;//遍历这个点之前将 b 标记为 1,表明到达b点的最短路径已经找过了
for(auto v : G[b]){//从 b 点可以到哪些点
if(dis[v.first] > dis[b] + v.second){//试图更新到 v 点的最短路径
dis[v.first] = dis[b] + v.second;
q.push({-dis[v.first], v.first});//将这个新的最短路径和点放进去
}
}
}
rep(i,1,n){
if(dis[i] == INF){
cout<<tmp-1<<(i==n?endl:" ");
}else{
cout<<dis[i]<<(i==n?endl:" ");}
}
return;
}欧拉筛 - 线性筛
时间复杂度 :
空间复杂度 :
int N = 1e7;
vi pr; //素数序列
vi b(N + 10); //记录每个值的最小质因子,原本初始化都为自己
vi f(N + 10, 1); //1代表为素数,0代表为非素数。一开始先假设所有的数都为素数
rep(i, 1, N){
b[i] = i; //初始化
}
f[0] = 0;
f[1] = 0;
rep(i, 2, N){
if(f[i] == 1) pr.push_back(i);
rep(j, 0, pr.size() - 1){
if(pr[j] * i > N){
break;
}
f[pr[j] * i] = 0;
b[pr[j] * i] = pr[j];
if(i % pr[j] == 0) break;
}
}加了最大质因数的优化版
int N = 1e7;
vi pr; //素数序列
vi b(N + 10); //记录每个值的最小质因子,原本初始化都为自己
vi c(N + 10); //记录最大质因数
vi f(N + 10, 1); //1代表为素数,0代表为非素数。一开始先假设所有的数都为素数
rep(i, 1, N){
b[i] = i; //初始化
c[i] = i;
}
f[0] = 0;
f[1] = 0;
rep(i, 2, N){
if(f[i] == 1) pr.push_back(i);
rep(j, 0, pr.size() - 1){
if(pr[j] * i > N){
break;
}
f[pr[j] * i] = 0;
b[pr[j] * i] = pr[j];
c[pr[j] * i] = max(c[i], pr[j]);
if(i % pr[j] == 0) break;
}
}模逆元 - 费小马定理
时间复杂度 :
空间复杂度 :
int qpow(int a,int b);
int mm(int x){
return qpow(x,mod-2);
}
(a / b) % mod -> a * mm (b) % mod;多重背包+二进制优化
int num=1;
rep(i,1,n){
cin>>v>>w>>s;
for(int j=1;j<=s;j<<=1){
vv[num]=j*v;//存体积
ww[num++]=j*w;存价值
}
if(s){
vv[num]=s*v;
ww[num++]=s*w;
}
}
01背包
rep(i,1,n)
rep(j,m,1){
}埃氏筛
int N=1e7;
vi f(N+10,1);记录是否为素数,1为素数,0为非素数,一开始初始化为假设所有数都为素数
vi pr;//存储素数
f[0]=0;
f[1]=0;
rep(i,2,N){
if(f[i]){
pr.push_back(i);
for(int j=i*i;j<=N;j+=i){
f[j]=0;
}
}
}
树状数组
时间复杂度 :
建树 : /
修改 / 查询 :
空间复杂度 :
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define vi vector<int>
#define vii vector<vector<int>>
const int N=4e5+10;
int lowbit(int x){
return x&(-x);
}
void solve(){
int n,m;cin>>n>>m;
vi a(n+10);
rep(i,1,n)cin>>a[i];
vi b(n+10);
rep(i,1,n){
int p=i;
while(p<=n){
b[p]+=a[i];
p+=lowbit(p);
}
}
rep(i,1,m){
int tmp;cin>>tmp;
if(tmp==1){
int x,cur;cin>>x>>cur;//加一个数时
int p=x;
while(p<=n){
b[p]+=cur;
p+=lowbit(p);
}
}else{
int x,y;cin>>x>>y;//查询[x,y]的和时
x-=1;
int p=x;
int re1=0;
int re2=0;
while(p>=1){
re1+=b[p];
p-=lowbit(p);
}
p=y;
while(p>=1){
re2+=b[p];
p-=lowbit(p);
}
cout<<re2-re1<<endl;
}
}
return;
}贝尔曼
处理含有边权为负数的最短路径
时间复杂度 :
空间复杂度 :
下面的代码主要为枚举每个点的出边
但是如果题目当中 和 相差不大,意味着这是一个稀疏图,那么我们可以在 里面枚举每条边,时间复杂度 ,但是实际运行时间会小得多
第 轮还被更新的点,说明达到这个点存在负环
vector<pair<int,int>> G[N];//记录有向边和边权
vi dis(n+10,INF);//记录最短路径
dis[1] = 0;
int flag=0;//判断每次循环是否有松弛操作发生,若没有发生,说明所有点的最短路径都已经找到,可以跳出循环。 如果循环结束的时候松弛操作还是发生了,说明存在负环,(也就是这个点的最短路径是负数且存在环,不断地循环重复路径,会让这个最短路径越来越小。
rep(i,1,n){
flag = 0;
rep(j, 1, n){
if(dis[j] == INF)continue;//说明这个点还没有到达过,用这个点去更新没有意义
for(auto v:G[j]){
if(dis[v.first] > dis[j] + v.second){
flag = 1;
dis[v.first] = dis[j] + v.second;
}
}
}
if(flag == 0)break;
}
if(flag == 1) cout<<"存在负环"<<endl;
rep(i,1,n)cout<<(dis[i]==INF?-1:dis[i])<<" \n"[i==n];贝尔曼优化 - SPFA
这个优化感觉有点神秘,我有一点看不懂…做题的时候尽量少用吧,除非前面时间复杂度太大了
vector<pair<int,int>> G[N]; //记录有向边和边权
vi dis(n + 10,INF); //记录最短路径
vi cnt(n + 10); //记录边数
vi vis(n + 1);
dis[s] = 0;
vis[s] = 1;
queue<int> q;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = 0;
for(auto v : G[u]){
if(dis[u] + v.second < dis[v.first]){
dis[v.first] = dis[u] + v.second;
cnt[v] = cnt[u] + 1;
if(cnt[v] >= n) 表示存在负环,然后跳出这一整个程序
if(vis[v.first] == 0) q.push(v.first);
vis[v.first] = 1;
}
}
}
如果没有前面跳出,那就相当于没有负环
rep(i,1,n)cout<<(dis[i]==INF?-1:dis[i])<<" \n"[i==n];区间dp
时间复杂度
模板题:石子合并
题意
有 堆石子排成一排,其编号为 1,2,3,...,N
每堆石子有一定的质量 ,现在要将这 堆石子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后于这两堆石子相邻的石子将与新堆相邻。
合并时由于选择的顺序不同,合并的总代价也不相同。
试找出一种合理的方法,使总的代价最小,并输出最小代价。
题解
用 表示合并 的最小代价
🐖: 表示本来那一堆,代价为 0
合并两堆和代价可以表示为:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define vi vector<int>
#define vii vector<vi>
const int INF = 1e18;
void solve(){
int n;cin>>n;
vi a(n+10);
vi pre(n+10);
vii f(n+10,vi(n+10,INF));
rep(i,1,n){
cin>>a[i];
pre[i]=pre[i-1]+a[i];
f[i][i]=0;//初始化
}
rep(len,2,n)//枚举区间长度
for(int l=1;l+len-1<=n;l++){//枚举左区间
int r=l+len-1;
rep(k,l,r-1)//枚举中间k
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+pre[r]-pre[l-1]);
}
cout<<f[1][n]<<endl;
return;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T = 1;
//cin>>T;
while(T--) solve();
return 0;
}线段树
变量名:
struct node{
int l; //左边界
int r; //右边界
int sum; //区间总值
int add; //懒标记
};
建树
//定义左节点,右节点 , 可以看上图
#define lc p<<1 //左移一位,相当于 p*2 ,速度更快
#define rc (p<<1) + 1//建立线段树
vector<node> tr(4 * n +1);
vector<int> w(n + 1);
void pushup(int p){
tr[p].sum = (tr[lc].sum + tr[rc].sum);
return;
}
void build(int p, int l, int r){
//先对这个tr[p]进行初始化
tr[p] = {l, r, 0, 0};
if(l == r) {
tr[p].sum = w[l];
return;
}
int m = (l + r) >> 1;
build(lc, l, m);
build(rc, m+1, r);
pushup(p);
return;
}
void solve(){
rep(i, 1, n) cin >> w[i];
build(1, 1, n); //分别代表,节点编号,区间左段,区间右端
}区间内每个点统一加 ——懒标记
//向下传懒标记
void pushdown(int p){
if(tr[p].add){
tr[lc].sum += tr[p].add * (tr[lc].r - tr[lc].l + 1);
tr[rc].sum += tr[p].add * (tr[rc].r - tr[rc].l + 1);
tr[lc].add += tr[p].add;
tr[rc].add += tr[p].add;
tr[p].add = 0;
}
return;
}
// p为节点编号 , x 要加 k 的区间的左端点 , y 为要加 k 的区间的右端点
void update(int p, int x, int y, int k){
//如果 p 指向的区间包含在 [x,y] 之内,那么就是 p 的区间内每个点都要加上 k
if(tr[p].l >= x && tr[p].r <= y){
tr[p].add += k;
tr[p].sum += k * (tr[p].r - tr[p].l + 1);
return;
}
pushdown(p);
int m = (tr[p].l + tr[p].r) >> 1;
if(x <= m) update(lc , x, y, k);
if(y >= m + 1) update(rc, x, y, k);
pushup(p);
}查询
//查询区间 , 总值为 sum
int query(int p, int x, int y){
if(tr[p].l >= x && tr[p].r <= y){
return tr[p].sum;
}
pushdown(p);
int sum = 0;
int m = (tr[p].l + tr[p].r) >> 1;
if(x <= m) query(lc, x, y);
if(y >= m + 1) query(rc, x, y);
return sum;
}单调栈
int n; cin >> n;
//这是找比自己大的
vi l(n + 1), r(n + 1), st;
vi a(n + 1);
rep(i, 1, n) cin >> a[i];
rep(i, 1, n){
if(!st.empty() && a[st.back()] <= a[i]) st.pop_back(); //删到更大为止
l[i] = st.size() == 0 ? 0 : st.back();
st.push_back(i);
}
st.clear();
per(i, n, 1){
if(!st.empty() && a[st.back()] <= a[i]) st.pop_back();
r[i] = st.size() == 0 ? 0 : st.back();
st.push_back(i);
}三分法
适用情况:遇到凸函数求峰点,或者遇到凹函数求谷点的时候
一共存在四个变量,分别为
示例代码:
int l = 0, r = n;
while(l < r){
int midl = (l + r) >> 1;
int midr = (midl + r) >> 1;
int sum1 = check(midl);
int sum2 = check(midr);
if(sum1 < sum2) l = midl;
else r = midr;
}
cout << l << endl;最长上升子序列
int lengthLIS(vector<int>& nums) {
vector<int> res;
for (int x : nums) {
auto it = lower_bound(res.begin(), res.end(), x);
if (it == res.end()) res.push_back(x);
else *it = x;
}
return res.size();
}没有指针版:
等哪天相通了再来写吧ST表
用于维护区间最值,与线段树维护区间最值的核心区别在于, 表无法中途进行某个值的修改或者某个区间的修改,但在仅仅维护最值这件事上,它得代码量更加少
变量解释:
二维数组, , 表示 这个区间的最值
vii st(21, vi (n + 1));
rep(i, 1, n) st[0][i] = a[i];
rep(k, 1, 20){ //构造
for(int j = 1; j + (1 << k) - 1 <= n; j++){
st[k][j] = max(st[k - 1][j], st[k][j + (1 << (k - 1))]);
}
}
rep(i, 1, q){ //查询
int l, r; cin >> l >> r;
int k = __lg(r - l + 1);
cout << max(st[k][l], st[k][r - (1 << k) + 1]);
}字典树
//以 0 为根节点出发
vii f(N, vi(30)); //一维记录根节点编号, 二维记录哪个字母, 记录这个的编号
int p = 1;
rep(i, 1, n){
string s; cin >> s;
int x = 0;
rep(j, 0, s.size() - 1){
if(!f[x][s[j]]){
f[x][s[j]] = p;
p ++;
x = p;
}else{
x = f[x][s[j]];
}
}
}