算法竞赛模板整理

算法竞赛模板整理

基础算法

快速排序

快速排序

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;

void quick_sort(int a[],int l,int r){
    if(l>=r)
        return;
    int x = a[l+r>>1], i = l - 1, j = r + 1;
    while (i<j)
    {
//        while(a[++i]<x);
//        while(a[--j]>x);
        do i++; while(a[i]<x);
        do j--; while(a[j]>x);
        if(i<j)
            swap(a[i],a[j]);
    }
    quick_sort(a,l,j);
    quick_sort(a,j+1,r);

}
int n;
int a[maxn];
int main()
{
    scanf("%d",&n);
    for (int i = 1;i<=n;i++){
        scanf("%d",&a[i]);
    }

    quick_sort(a, 1, n);
    for (int i = 1;i<=n;i++){
        printf("%d ",a[i]);
    }
    return 0;
}

快速选择算法—第k个数

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n,k;
int quick_sort(int l,int r,int k){
    if(l>=r) return a[l];
    int x=a[l+r>>1],i=l-1,j=r+1;
    while(i<j){
        while(a[++i]<x);
        while(a[--j]>x);
        if(i<j) swap(a[i],a[j]);
    }
    int sl=j-l+1;
    if(k<=sl) return quick_sort(l,j,k);
    else return quick_sort(j+1,r,k-sl);
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    cout<<quick_sort(1,n,k)<<"\n";
}

归并排序

归并排序

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn],tmp[maxn];

void merge_sort(int a[],int l,int r){
    if(r<=l) return ;
    int mid=l+r>>1;
    merge_sort(a,l,mid);
    merge_sort(a,mid+1,r);
    int k=0,i=l,j=mid+1;
    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(i=l,j=1;i<=r;i++,j++) a[i]=tmp[j];

}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    merge_sort(a,1,n);
    for(int i=1;i<=n;i++){
        printf("%d ",a[i]);
    }
}

逆序对数量

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
int a[maxn],tmp[maxn];
int n;

ll merge_sort(int l,int r){
    if(l>=r) return 0;
    int mid=l+r>>1;
    ll res=0;
    res+=merge_sort(l,mid);
    res+=merge_sort(mid+1,r);
    int k=0,i=l,j=mid+1;
    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(i=l,j=1;i<=r;i++) a[i]=tmp[j++];
    return res;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    cout<<merge_sort(1,n)<<"\n";
}

二分

模板

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

数的范围
对于每个查询,返回一个元素k的起始位置和终止位置

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n,q;
int main() {
    scanf("%d%d",&n,&q);
    for(int i=1; i<=n; i++) {
        scanf("%d",&a[i]);
    }
    while(q--) {
        int x;
        scanf("%d",&x);
//        if(a[lower_bound(a+1,a+n+1,x)-a]==x) {
//            cout<<lower_bound(a+1,a+n+1,x)-a-1<<" "<<upper_bound(a+1,a+n+1,x)-a-2<<"\n";
//        } 
//        else {
//            cout<<"-1 -1\n";
//        }
        int l=0,r=n;
        while(l<r) {
            int mid=l+r>>1;
            if(a[mid]>=x) r=mid;
            else l=mid+1;
        }
        if(a[l]!=x) {
            cout<<"-1 -1\n";
        } else {
            cout<<l-1<<" ";
            l=0,r=n;
            while(l<r) {
                int mid=l+r+1>>1;
                if(a[mid]<=x) l=mid;
                else r=mid-1;
            }
            cout<<l-1<<"\n";
        }
    }
}

数的三次方根

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int main(){
    double n;
    scanf("%lf",&n);
    //printf("%lf\n",cbrt(n));
    double sign=n>0?1:-1;
    n=fabs(n);
    double l=0,r=n;
    while(r-l>1e-8){
        double mid=(l+r)/2;
        if(mid*mid*mid<n) l=mid;
        else r=mid;
    }
    printf("%lf\n",l*sign);
}

高精度

高精度加法

#include<bits/stdc++.h>
using namespace std;
vector<int> add(vector<int> &A,vector<int> &B){
    vector<int>C;
    int t=0;
    for(int i=0;i<A.size()||i<B.size();i++){
        if(i<A.size()) t+=A[i];
        if(i<B.size()) t+=B[i];
        C.push_back(t%10);
        if(t<10) t=0;
        else t=1;
    }
    if(t) C.push_back(t);
    return C;
}
int main(){
    string a,b;
    cin>>a>>b;
    vector<int>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');
    auto C=add(A,B);
    for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
}

高精度减法

#include<bits/stdc++.h>
using namespace std;
vector<int> sub(vector<int> &A,vector<int> &B){
    vector<int>C;
    int t=0;
    for(int i=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;
}
string a,b;
int main(){
    int sign=0;
    cin>>a>>b;
    if(a.size()<b.size()||a.size()==b.size()&&a<b) swap(a,b),sign=1;
    vector<int>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');
    auto C=sub(A,B);
    printf(sign?"-":"");
    for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
    return 0;
}

高精度乘法

#include<bits/stdc++.h>
using namespace std;
vector<int> mul(vector<int> &A,int b){
    vector<int>C;
    int t=0;
    for(int i=0;i<A.size();i++){
        t=t+A[i]*b;
        C.push_back(t%10);
        t=t/10;
    }
    while(t)C.push_back(t%10),t/=10;
    return C;
}
string a;
int b;
int main(){
    cin>>a>>b;
    vector<int>A;
    for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
    auto C=mul(A,b);
    for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
    return 0;
}

高精度除法

#include<bits/stdc++.h>
using namespace std;
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;
}
string a;
int b,r;
int main(){
    cin>>a>>b;
    vector<int>A;
    for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
    auto C=div(A,b,r);
    for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
    cout<<"\n"<<r;
    return 0;
}

前缀和和差分

一维前缀和

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

二维前缀和

int get_sum(int x1,int y1,int x2,int y2){
    return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];

一维差分

void insert(int l,int r,int k){
    d[l]+=k,d[r+1]-=k;
}

二维差分

void insert(int x1,int y1,int x2,int y2,int k){
    d[x1][y1]+=k;
    d[x2+1][y1]-=k;
    d[x1][y2+1]-=k;
    d[x2+1][y2+1]+=k;
}

位运算

求n的第k位数字: n >> k & 1
返回n的最后一位1lowbit(n) = n & -n
2进制下1的个数:__builtin_popcount(n);

离散化

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
int get_id(int x){
    return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1; //二分查询离散值
}

区间合并

将所有存在交集的区间合并

void merge(vector<pii> &segs) {
    vector<pii>res;
    sort(segs.begin(),segs.end());
    int st=-2e9,en=-2e9;
    for(auto it:segs) {
        if(it.first>en) {
            if(st!=-2e9) res.push_back({st,en});
            st=it.first,en=it.second;
        } else en=max(en,it.second);
    }
    if(st!=-2e9) res.push_back({st,en});
    segs=res;
}

写在后面

1.函数传引用避免拷贝时间
2.__builtin_popcount()很香
3.快排,归并,二分注意处理边界问题

数据结构

单链表

//单链表
int head,e[maxn],ne[maxn],idx=0;
int n;
//头结点最开始指向0,插入第一个值idx=1;
void add_head(int x){
    e[++idx]=x;
    ne[idx]=head;
    head=idx;
}
void remove(int k){
    ne[k]=ne[ne[k]];
}
void add(int k,int x){
    e[++idx]=x;
    ne[idx]=ne[k];
    ne[k]=idx;
}
for(int i=head;i;i=ne[i]){
     printf("%d ",e[i]);
}

双链表

int e[maxn],ne[maxn],pr[maxn],idx=1;
int n;
// e[]表示节点的值,pr[]表示节点的左指针,ne[]表示节点的右指针,idx表示当前用到了哪个节点
void init(){
   //0是左端点,1是右端点
    ne[0]=1;
    pr[1]=0;
}
// 在节点k的右边插入一个数x
void add(int k,int x){
    e[++idx]=x;
    ne[idx]=ne[k];
    pr[idx]=k;
    pr[ne[k]]=idx;
    ne[k]=idx;
}
// 删除节点k
void remove(int k){
    ne[pr[k]]=ne[k];
    pr[ne[k]]=pr[k];
}

// tt表示栈顶
int st[maxn], tt = 0;
// 向栈顶插入一个数
st[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
st[tt];
// 判断栈是否为空
if (tt > 0) {

}

队列

// hh 表示队头,tt表示队尾
int qu[maxn],hh=1,tt=0;
// 向队尾插入一个数
qu[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
qu[hh];
// 判断队列是否为空
if (hh <= tt) {

}

单调栈

常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ ){
    while(tt&&check(stk[tt])) tt--;
    stk[++tt]=i  or a[i];
}

单调队列

常见模型:找出滑动窗口中的最大值/最小值
int hh = 1, tt = 0;
for (int i = 1; i <=n; i ++ ) {
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判断队头是否滑出窗口
    while (hh <= tt && check(q[tt],i)) tt -- ;
    q[ ++ tt] = i  or  a[i];
}

KMP

char s[maxn],p[maxn];
int ne[maxn];
void get_next(char *s){
    int m=strlen(s+1);
    int i=1,j=0;
    ne[1]=0;
    while(i<=m){
        if(j==0||s[i]==s[j])
            ne[++i]=++j;
        else
            j=ne[j];
    }
}
int kmp(char *s,char *p){
    int i=1,j=1;
    int n=strlen(s+1),m=strlen(p+1);
    while(i<=n){
        if(j==0||s[i]==p[j]){
            ++i,++j;
        }
        else
            j=ne[j];
        if(j==m+1){
            return 1; //p为s的子串
        }
    }
    return 0;
}

Trie

char s[maxn];
int tr[maxn][26],cnt[maxn],idx;
// maxn为n*len
// 0号点既是根节点,又是空节点
// tr[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(char *s){
    int p=0;
    for(int i=0;s[i];i++){
        int u=s[i]-'a';
        if(!tr[p][u]) tr[p][u]=++idx;
        p=tr[p][u];
    }
    cnt[p]++;
}
// 查询字符串出现的次数
int qurey(char *s){
    int p=0;
    for(int i=0;s[i];i++){
        int u=s[i]-'a';
        if(!tr[p][u]) return 0;
        p=tr[p][u];
    }
    return cnt[p];
}

并查集

朴素并查集

//查询祖先编号
int fin(int x){
    return re[x]==x?x:re[x]=fin(re[x]);
}
for(int i=1;i<=n;i++) re[i]=i;

//合并
int fx=fin(x),fy=fin(y);
    re[fx]=fy;

种类并查集(食物链)

int re[maxn],d[maxn]; 		//d[x]存储x到re[x]的距离
int fin(int x){
    if(re[x]!=x){
        int fa=fin(re[x]);  //找到祖先
        d[x]+=d[re[x]];     //到祖先的距离等于x到父亲的距离加上父亲到祖先的距离
        re[x]=fa;           //路径压缩
    }
    return re[x];           //返回父亲节点
}

//初始化
for(int i=1;i<=n;i++) re[i]=i,d[i]=0;

//合并集合
if(fx!=fy){
    re[fx]=fy;
    d[fx]=distance; // 根据具体问题,初始化fx的偏移量
}

小根堆
等同于priority_queue的堆

int heap[maxn],idx;
int n,m;
/* 小根堆
1.插入一个数         heap[++idx]=x;up(idx);
2.求集合当中最小值    heap[1];
3.删除最小值         swap(heap[1],heap[idx]);idx--;down(1);
4.删除任意一个元素    heap[k]=heap[idx];idx--;down(k);up(k);
5.修改任意一个元素    heap[k]=x;down(k);up(k);
6.创建堆 O(n)       for(int i=n/2;i>=1;i--) down(i);
*/
void down(int u){
    int v=u;
    if(u*2<=idx&&heap[u*2]<heap[v]) v=u*2;
    if(u*2+1<=idx&&heap[u*2+1]<heap[v])v=u*2+1;
    if(u!=v){
        swap(heap[u],heap[v]);
        down(v);
    }
}
void up(int u){
    while(u/2&&heap[u/2]>heap[u]){
        swap(heap[u/2],heap[u]);
        u=u/2;
    }
}

可以删除修改第k个插入的元素
维护2个数组,堆下标->数组下标 and 数组下标->堆下标

int heap[maxn],hp[maxn],ph[maxn],idx;
//heap为堆,hp为堆上编号映射到数组下标,ph为数组下标映射到堆上编号
void heap_swap(int u,int v){
    swap(ph[hp[u]],ph[hp[v]]);
    swap(hp[u],hp[v]);
    swap(heap[u],heap[v]);
}
void up(int u){
    while(u/2&&heap[u/2]>heap[u]){
        heap_swap(u/2,u);
        u=u/2;
    }
}
void down(int u){
    int t=u;
    if(u*2<=idx&&heap[u*2]<heap[t]) t=u*2;
    if(u*2+1<=idx&&heap[u*2+1]<heap[t]) t=u*2+1;
    if(t!=u){
        heap_swap(u,t);
        down(t);
    }
}
//插入
 heap[++idx]=x;
 ph[++pos]=idx,hp[idx]=pos;
 up(idx);
//堆顶
 cout<<heap[1]<<"\n";
//删除堆顶
 heap_swap(1,idx);
 idx--;
 down(1);
//删除第k个插入的元素
 k=ph[k];
 heap_swap(idx,k);
 idx--;
 down(k);
 up(k);
//修改第k个插入的元素
 k=ph[k];
 heap[k]=x;
 down(k);
 up(k);

哈希

一般哈希

/*开放寻址法*/
const int N = 200003, null = 0x3f3f3f3f;
//N为散列表大小,开2-3倍且为质数
//null为初始值,表明此位置没有被赋值过
int H[N];
int fin(int x){
    int t = (x % N + N) % N;
    while(H[t]!=null&&H[t]!=x){
        t++;
        if(t==N) t=0;
    }
    return t;
}
//初始化
memset(H,0x3f,sizeof H);
//插入
int t=fin(x);
H[t]=x;
//查找
cout<<(H[t]==x?"Yes\n":"No\n");
/*拉链法*/
const int N=100003; 
//数据范围大小一倍且为质数
int H[N],e[N],ne[N],idx;
void insert(int x){
    int t = (x % N + N) % N;
    e[++idx]=x;ne[idx]=H[t];H[t]=idx;
}
int find(int x){
     int t = (x % N + N) % N;
     for(int i=H[t];i;i=ne[i]){
         if(e[i]==x) return 1;
     }
     return 0;
}
//初始化
memset(H,0,sizeof H);
//插入
insert(x);
//查找
cout<<(find(x)?"Yes\n":"No\n");

字符串哈希

typedef unsigned long long ull;
const int maxn=1e5+5,P=131;
//随机模数P的经验值是131或13331,取这两个值的冲突概率低
ull H[maxn],base[maxn]; // h[k]存储字符串前k个字母的哈希值, base[k]存储 P^k mod 2^64

//预处理
base[0] = 1;
for (int i = 1; i <= n; i ++ ){
    H[i] = H[i - 1] * P + s[i];
    base[i] = base[i - 1] * P;
}

// 计算子串 s[l ~ r] 的哈希值
ull get_hash(int l,int r){
    return H[r] - H[l - 1] * base[r - l + 1];
}

搜索与图论

DFS

递归,遍历所有情况,使用栈

BFS

解决最小步数问题,层次遍历,使用队列

树与图的存储

1.邻接矩阵
2.vector存图
3.邻接表

树的重心

int dfs(int u){
    vi[u]=true;siz[u]=1;
    int sum=0;
    for(int i=h[u];i;i=ne[i]){
        int v=e[i];
        if(vi[v]) continue ;
        int t=dfs(v);
        sum=max(sum,t);
        siz[u]+=t;
    }
    sum=max(sum,n-siz[u]);
    ans=min(ans,sum);
    return siz[u];
}
//ans为所求

树的直径

void dfs(int u, int fa){
    for (int i = 0; i < edge[u].size(); i++){
        int v = edge[u][i].first, val = edge[u][i].second;
        if (v == fa)
            continue;
        dfs(v, u);
        if (dp[v][0] + val > dp[u][0])
            dp[u][1] = dp[u][0], dp[u][0] = dp[v][0] + val;
        else if (dp[v][0] + val > dp[u][1])
            dp[u][1] = dp[v][0] + val;
    }
    ans = max(dp[u][0] + dp[u][1], ans);
}
//ans为所求

拓扑排序

时间复杂度 O(n+m)

void tupo(){
    queue<int>q;
    for(int i=1;i<=n;i++){
        if(!in[i]) q.push(i),ans.push_back(i);
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        for(int i=h[t];i;i=ne[i]){
            int v=e[i];
            if(--in[v]) continue;
            q.push(v),ans.push_back(v);
        }
    }
    if(ans.size()==n)// 存在拓扑排序,即不存在环
    //拓扑排序不唯一,可用来判环
}

Dijkstra朴素版本

时间复杂是 O(n2+m)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5, inf = 0x3f3f3f3f, N = 505;
int n, m;
int g[N][N], dis[N];
bool vis[N];
int dijkstra(int st, int en){
    memset(dis, 0x3f, sizeof dis);
    dis[st] = 0;
    for(int j=1;j<=n;j++){
        int t=-1;
        for(int i=1;i<=n;i++){
            if(!vis[i]&&(t==-1||dis[i]<dis[t]))
                t=i;
        }
        vis[t]=true;
        for(int i=1;i<=n;i++){
            if(dis[i]>dis[t]+g[t][i])
                dis[i]=dis[t]+g[t][i];
        }
    }
    if(dis[en]==inf) return -1;
    return dis[en];
}
int main()
{
    memset(g, 0x3f, sizeof g);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        g[i][i] = 0;
    while (m--){
        int x, y, z;
        cin >> x >> y >> z;
        g[x][y] = min(g[x][y], z);
    }
    cout << dijkstra(1, n) << "\n";
}

Dijkstra堆优化版本

时间复杂度 O(mlogn)

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m;
int e[N],w[N],ne[N],h[N],idx;
int dis[N];
bool vis[N];
void add(int x,int y,int z){
    ++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
int Dijkstra(int st,int en){
    memset(dis,0x3f,sizeof dis);
    priority_queue<pii,vector<pii>,greater<pii> >q;
    dis[st]=0;
    q.push({0,st});
    while(q.size()){
        auto t=q.top();
        q.pop();
        int u=t.second,distance=t.first;
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=h[u];i;i=ne[i]){
            int v=e[i];
            if(dis[v]>distance+w[i]){
                dis[v]=distance+w[i];
                q.push({dis[v],v});
            }
        }
    }
    if(dis[en]==inf) return -1;
    return dis[en];
}
int main(){
    cin>>n>>m;
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    cout<<Dijkstra(1,n)<<"\n";
}

Bellman-Ford算法

时间复杂度 O(nm)
可求最多经过k条边的最短路

#include <bits/stdc++.h>
using namespace std;
const int N=505,M=1e5+5,inf=0x3f3f3f3f;
int n,m,k;
int dis[N],backup[N];
struct node{
    int x,y,val;
}edge[M];
//O(n*m)
int bellman_ford(int st,int en){
    memset(dis,0x3f,sizeof dis);
    dis[st]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            auto t=edge[j];
            dis[t.y]=min(dis[t.y],dis[t.x]+t.val);
        }
    }
    if(dis[en]>inf/2) return -1;
    return dis[en];
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        edge[i]={x,y,z};
    }
    int ans=bellman_ford(1,n);
    cout<<ans<<"\n";
}

spfa算法

时间复杂度 平均情况下 O(m),最坏情况下 O(nm)。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5,inf=0x3f3f3f3f;
int e[M],ne[M],w[M],h[N],idx;
int dis[N];
bool vis[N];
int n,m;
//O(k*n)
void add(int x,int y,int z){
    ++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
int spfa(int st,int en){
    memset(dis,0x3f,sizeof dis);
    dis[st]=0;

    queue<int>q;
    q.push(st);
    vis[st]=true;

    while(q.size()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=h[u];i;i=ne[i]){
            int v=e[i],val=w[i];
            if(dis[v]>dis[u]+val){
                dis[v]=dis[u]+val;
                if(!vis[v]){// 如果队列中已存在v,则不需要将v重复插入
                    q.push(v);
                    vis[v]=true;
                }
            }
        }
    }
    if(dis[en]==inf) return -1;
    return dis[en];
}
int main(){
    cin>>n>>m;
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    int ans=spfa(1,n);
    if(ans==-1) puts("impossible");
    else cout<<ans<<"\n";
}

spfa判断负环

时间复杂度是 O(nm)

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5,inf=0x3f3f3f3f;
int e[M],ne[M],w[M],h[N],idx;
int cnt[N],dis[N];
bool vis[N];
int n,m;
//O(n*m)
void add(int x,int y,int z){
    ++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
bool SPFA(){

	// 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
    queue<int>q;
    for(int i=1;i<=n;i++){
        q.push(i);
        vis[i]=true;
    }

    while(q.size()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=h[u];i;i=ne[i]){
            int v=e[i],val=w[i];
            if(dis[v]>dis[u]+val){
                dis[v]=dis[u]+val;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n) return true;	//cnt[v]表示到v所经过n条边,即需要经过n+1个点,但只存在n个点松弛过程中必然出现一个点经过两次,即负环
                if(!vis[v]){	
                    q.push(v);
                    vis[v]=true;
                }
            }
        }
    }
    return false;
}
int main(){
    cin>>n>>m;
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    if(SPFA()) cout<<"Yes\n";
    else cout<<"No\n";
}

floyd算法

时间复杂度是 O(n^3)

#include<bits/stdc++.h>
using namespace std;
const int N=205,inf=0x3f3f3f3f;
int n,m,k;
int g[N][N];
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            g[i][j]=i==j?0:inf;
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        g[x][y]=min(g[x][y],z);
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
    while(k--){
        int u,v;
        cin>>u>>v;
        if(g[u][v]>inf/2) puts("impossible");
        else cout<<g[u][v]<<"\n";
    }
}

最小生成树

prim算法
时间复杂度O(n^2)

#include<bits/stdc++.h>
using namespace std;
const int N=505,inf=0x3f3f3f3f;
int n,m;
int dis[N],g[N][N];
bool vis[N];

int prim(){
    memset(dis,0x3f,sizeof dis);
    int res=0;
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&(t==-1||dis[j]<dis[t]))
                t=j;
        }
        if(i&&dis[t]==inf) return -1;
        vis[t]=true;
        if(i) res+=dis[t];
        for(int j=1;j<=n;j++){
            dis[j]=min(dis[j],g[t][j]);
        }  
    }
    return res;
}

int main(){
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        g[x][y]=g[y][x]=min(g[x][y],z);
    }
    int ans=prim();
    if(ans==-1) puts("impossible");
    else cout<<ans<<"\n";
    

Kruskal算法
时间复杂度O(mlogm)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5, inf = 0x3f3f3f3f;
int n, m;
int re[N];
struct node{
    int x, y, val;
    friend bool operator<(const node a, const node b){
        return a.val < b.val;
    }
} edge[M];
int fin(int x){
    return re[x] == x ? re[x] : re[x] = fin(re[x]);
}
int kruscal()
{
    int res = 0, cnt = 0;
    sort(edge + 1, edge + m + 1);
    for (int i = 1; i <= m; i++){
        int fx = fin(edge[i].x), fy = fin(edge[i].y);
        if (fx != fy){
            re[fx] = fy;
            res += edge[i].val;
            cnt++;
        }
    }
    if (cnt < n - 1) return -1;
    return res;
}
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) re[i] = i;
    for (int i = 1; i <= m; i++){
        int x, y, z;
        cin >> x >> y >> z;
        edge[i] = {x, y, z};
    }
    int ans = kruscal();
    if (ans == -1) puts("impossible");
    else cout << ans << "\n";
}

染色法判定二分图

时间复杂度O(n+m)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
int e[N],ne[N],h[N],idx;
int color[N];// 表示每个点的颜色,0表示为未染色,1表示黑色,2表示白色
void add(int x,int y){
    e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
// 奇偶染色
// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u,int c){
    color[u]=c;
    for(int i=h[u];i;i=ne[i]){
        int v=e[i];
        if(!color[v]){
            if(!dfs(v,3-c)) return false;
        }
        else if(color[v]==c) return false;
    }
    return true;
   
}
int main(){
    cin>>n>>m;
    while(m--){
        int x,y;
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;i++){
        if(!color[i]){
            if(!dfs(i,1)){
                puts("No");
                return 0;
            }
        }
    }
    puts("Yes");
}

匈牙利算法

二分图最大匹配
时间复杂度O(n*m)

#include<bits/stdc++.h>
using namespace std;
const int N=505,M=1e5+5;
int e[M],ne[M],h[N],idx;// 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int n1,n2,m;// n1表示第一个集合中的点数,n2表示第二个集合中的点数
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool vis[N];// 表示第二个集合中的每个点是否已经被遍历过
void add(int x,int y){
    e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
int Hungary(int u){
   for(int i=h[u];i;i=ne[i]) {
       int v=e[i];
       if(vis[v]) continue;
       vis[v]=1;
       if(!match[v]||Hungary(match[v])){
           match[v]=u;
           return true;
       }
   }
   return false;
   
}
int main(){
    cin>>n1>>n2>>m;
    while(m--){
        int x,y;
        cin>>x>>y;
        add(x,y);
    }
    int ans=0;
    // 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
    for(int i=1;i<=n1;i++){
        memset(vis,0,sizeof(vis));
        ans+=Hungary(i);
    }
    cout<<ans<<"\n";
}

数学知识

质数

判断质数

bool check_prime(int x){
    if(x<2) return false;
    for(int i=2;i<=x/i;i++){
        if(x%i==0) return false;
    }
    return true;
}

分解质因数

vector<pii>v;
void solve(int n){
    v.clear();
    for(int i=2;i<=n/i;i++){
        if(n%i==0){
            int cnt=0;
            while(n%i==0) n/=i,cnt++;
            v.push_back({i,cnt});
        }
    }
    if(n>1) v.push_back({n,1});
}

素数筛

bool is_prime[maxn];
int prime[maxn],p_cnt;
void get_prime(int n){
    is_prime[0]=is_prime[1]=-1;//非质数非合数
    for(int i=2;i<=n;i++){
        if(!is_prime[i]) prime[++p_cnt]=i;
        for(int j=1;prime[j]<=n/i;j++){
            is_prime[prime[j]*i]=true;
            if(i%prime[j]==0)  break;
        }
    }
}

约数

求所有约数

void get_yue(int n){
    vector<int>v;
    for(int i=1;i<=n/i;i++){
        if(n%i==0){
            v.push_back(i);
            if(i!=n/i) v.push_back(n/i);
        }
    }
    sort(v.begin(),v.end());
}

约数个数

如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)

约数之和

如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

最大公约数

int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

欧拉函数

求欧拉函数

//phi(n)=n*(1-1/p1)*(1-1/p2)*...*(1-1/pm);
int get_phi(int n){
    int res=n;
    for(int i=2;i<=n/i;i++){
        if(n%i==0){
            res=res/i*(i-1);
            while(n%i==0) n/=i;
        }
    }
    if(n>1) res=res/n*(n-1);
    return res;
}

筛法求欧拉函数

int prime[maxn],phi[maxn],p_cnt;
bool is_prime[maxn];
void get_phi(int n){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!is_prime[i]) prime[++p_cnt]=i,phi[i]=i-1;
        for(int j=1;prime[j]<=n/i;j++){
            is_prime[i*prime[j]]=true;phi[i*prime[j]]=phi[i]*(prime[j]-1);
            if(i%prime[j]==0) {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
        }
    }
}

快速幂

int fast_pow(int a,int b,int p){
    a%=p;
    if(a==0) return 0;
    int res=1;
    while(b){
        if(b&1) res=1ll*res*a%p;
        a=1ll*a*a%p,b>>=1;
    }
    return res;
}

扩展欧几里得

// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a,int b,int &x,int &y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
    //x=x+(b/d*K);
    //y=y-(a/d*K);
}

高斯消元

double a[N][N];
int gauss(double a[][N]){
    int c,r;
    for(c=1,r=1;c<=n;c++) //c位列 r为行
    {
        int t=r;
        for(int i=r;i<=n;i++){
            if(fabs(a[i][c])>fabs(a[t][c]))
                t=i;
        }
        if(fabs(a[t][c])<eps) continue;
        for(int j=c;j<=n+1;j++) swap(a[t][j],a[r][j]);
        for(int j=n+1;j>=c;j--) a[r][j]/=a[r][c];
        for(int i=r+1;i<=n;i++)
            if(fabs(a[i][c])>eps)
                for(int j=n+1;j>=c;j--)
                    a[i][j]-=a[r][j]*a[i][c];
        r++;
    }
    if(r<=n){
        for(int i=r;i<=n;i++)
            if(fabs(a[i][n+1])>eps)
                return 2;//无解
        return 1;//多解
    }
    for(int i=n;i>=1;i--){
        for(int j=i+1;j<=n;j++)
            a[i][n+1]-=a[i][j]*a[j][n+1];
    }
    return 0;   //有唯一解
}

组合数

C(n,m)=C(n-1,m)+C(n-1,m-1)
n=1000

void C_init(int n){
    for(int i=0;i<=n;i++){
        C[i][0]=1;
        for(int j=1;j<=i;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
}

预处理逆元

int fac[maxn],invfac[maxn];
inline int C(int n,int m){return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;}
void C_init(int N){
    fac[0]=1;
    for(int i=1;i<=N;i++){
        fac[i] = 1ll*fac[i-1]*i%mod;
    }
    invfac[N] = fast_pow(fac[N],mod-2,mod);
    for(int i=N-1;i>=0;i--) invfac[i] = 1ll*invfac[i+1]*(i+1)%mod;  //1/(i-1)!=i/(i)!
}

Lucas
若p是质数,则对于任意整数 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

mod=1e6

int C(int n,int m,int p){
    if(m>n) return 0;
    int up=1,down=1;
    for(int i=1,j=n;i<=m;i++,j--){
        up=1ll*up*j%p;
        down=1ll*down*i%p;
    }
    return 1ll*up*fast_pow(down,p-2,p)%p;
}
int lucas(ll n,ll m,int p){
    if(n<p&&m<p) return C(n,m,p);
    return 1ll*C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}

高精度

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int prime[maxn],is_prime[maxn],mp[maxn];
int cnt_p;
void get_prime(int n){ // 线性筛法求素数
    for(int i=2;i<=n;i++){
        if(!is_prime[i]) prime[++cnt_p]=i;
        for(int j=1;prime[j]<=n/i;j++){
            is_prime[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int get(int n,int p){// 求n!中p 的次数
    int res=0;
    while(n){
        res+=n/p;
        n/=p;
    }
    return res;
}
vector<int>mul(vector<int>&A,int b){ // 高精度乘低精度模板
    vector<int>C;
    int t=0;
    for(int i=0;i<A.size();i++){
        t=A[i]*b+t;
        C.push_back(t%10);
        t=t/10;
    }
    while(t) C.push_back(t%10),t/=10;
    return C;
}

int main(){
    get_prime(5000);
    int n,m;
    cin>>n>>m;
    for(int i=1;prime[i]<=n;i++){
        int p=prime[i];
        mp[p]=get(n,p)-get(m,p)-get(n-m,p);
    }
    vector<int>res(1,1);
    for(int i=1;prime[i]<=n;i++){
        int p=prime[i];
        for(int j=1;j<=mp[p];j++){
            res=mul(res,p);
        }
    }
    reverse(res.begin(),res.end());
    for(auto it:res) cout<<it;
    puts("");
    return 0;
}

卡特兰数

给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为:
Cat(n) = C(2n, n) / (n + 1)

SG函数

在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值