16年NOIP复赛前各种模板的整理

本文整理了NOIP竞赛中常用的基础操作、数据结构和算法模板,包括输入输出、线段树、树状数组、最短路算法、最小生成树、数学方法等,为参赛者提供参考。
摘要由CSDN通过智能技术生成

各种模板的整理

题记:noip复赛快要开始了,想想整理模板大概会有点用吧。

一、基础:

作为比赛的基础的话肯定是读入输出,打开文件吧。

1、打开文件:

#include<cstdio>
int main(){
    freopen("*.in","r",stdin);
    freopen("*.out","w",stdout);

    return 0;
}

c++固定格式,不过用DEV会比较奇怪,即使不加上cstdio好像也不会出错。

这里正常的读入输出都不在重复,补充输入输出挂:

2、输入挂:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void Rd(int &x){
    char c;
    x=0;
    int f=1;
    while(c=getchar(),!isdigit(c)&&c!='-');
    if(c=='-')c=getchar(),f=-1;
    do x=(x<<3)+(x<<1)+(c^'0');
    while(c=getchar(),isdigit(c));
    x=x*f;
}

其中那条isdigit用于判断该字符是否为函数,但我不太清楚在哪个头文件里。负数需特判.

3、输出挂:

void Print(int x){
    if(x==0)return;
    Print(x/10);
    putchar((x%10)^'0');
}
void Pf(int x){
    if(x<0)putchar('-'),x=-x;
    else if(x==0){putchar('0');return;}
    Print(x);
}

这里输出挂同样支持负数。

二、数据结构:

1、线段树:


struct Tree{

    int A[M];

    struct node{
        int l,r,v,las;
    }tree[M*4];

    void up(int p){


    }

    void down(int p){


    }

    #define Ls(x) (x<<1)
    #define Rs(x) (x<<1|1)

    void build(int l,int r,int p){//建树
        tree[p].l=l,tree[p].r=r;
        if(l==r){
//          tree[p].v=A[l];
            return;
        }
        int mid=l+r>>1;
        build(l,mid,Ls(p));
        build(mid+1,r,Rs(p));
        up(p);
    }

    void update(int l,int r,int a,int p){//更新
        if(tree[p].l==l&&tree[p].r==r){
//          tree[p].v=(a);
            return;
        }
        down(p);
        int mid=tree[p].l+tree[p].r>>1;
        if(mid<l)update(l,r,a,Rs(p));
        else if(mid>=r)update(l,r,a,Ls(p));
        else update(l,mid,a,Ls(p)),update(mid+1,r,a,Rs(p));
        up(p);
    }

    int query(int l,int r,int p){//查询
//      if(tree[p].l==l&&tree[p].r==r)return tree[p].v;
        int mid=tree[p].l+tree[p].r>>1;
        down(p);
        if(mid<l)return query(l,r,Rs(p));
        else if(mid>=r)return query(l,r,Ls(p));
        else return (query(l,mid,Ls(p)),query(mid+1,r,Rs(p)));
    }

}Tr;

这里线段树是可以延迟更新的,down和up不具体写明,但是四倍内存一定要开到,否则很容易爆数组,复杂度 O(nlog2n)

2、树状数组:

struct Bit{
    int num[M],n;

    void Add(int x,int a){//更新
        while(x<=n){
//          num[x]=(a);
            x+=(x&-x);
        }
    }

    int Ask(int x){//查询
        int res=0;
        while(x){
//          res=(num[x]);
            x-=(x&-x);
        }
        return res;
    }

}Tr;

树状数组同样不具体写明应用,树状数组只需要开一倍内存,比较于线段树常数较小,而且简单精巧,复杂度同样 O(nlog2n)

3、并查集(最基本版)

int Fa[M];
int Find(int x){
    if(Fa[x]==x)return x;
    return Fa[x]=Find(Fa[x]);
}
void Init(){//预处理

    for(int i=1;i<=n;i++)Fa[i]=i;

}

带权并查集这里不表,复杂度较为玄学,目测可以当 O(n) 来算(因为常数非常小),不过最好还是当成 O(nlog2n)

4、ST表:

int Lg2[M];

struct ST{
    int num[S][M],n,A[M];

    void Init(){ //预处理

        Lg2[0]=-1;
        for(int i=1;i<=n;i++)Lg2[i]=Lg2[i>>1]+1;

        for(int i=1;i<=n;i++)num[0][i]=i;

        for(int i=1;i<S;i++){
            for(int j=1;j<=n;j++){
                int k=j+(1<<(i-1));
                if(k<=n){
                    int a=num[i-1][j],b=num[i-1][k];
                    if(A[a]>A[b])num[i][j]=b;
                    else num[i][j]=a;
                }else num[i][j]=num[i-1][j];
            }
        }

    }

    int query(int l,int r){//查询
        int k=Lg2[r-l+1];
        int a=num[k][l],b=num[k][r-(1<<k)+1];
        return A[a]>A[b]?b:a;
    }

}St;

这里的ST表为求最小值的版本,其中的S为 log2M ,复杂度为 O(nlog2n) ,查询 O(1)

4、BigInt:

struct Big{
    int num[M],len;

    Big(){memset(num,0,sizeof(num)),len=1;};//清零 

    bool operator <(const Big &a)const{// 判断大小 

        if(len<a.len)return true;
        if(len>a.len)return false;

        for(int i=len;i>=1;i--){
            if(num[i]<a.num[i])return true;
            if(num[i]>a.num[i])return false;
        }

        return false;

    }

    Big operator +(const Big &a)const{//高精加 

        Big res;
        res.len=max(len,a.len);
        for(int i=1;i<=res.len;i++){
            res.num[i]+=num[i]+a.num[i];
            res.num[i+1]+=res.num[i]/P;
            res.num[i]%=P;
        }

        if(res.num[res.len+1])res.len++;
        return res;
    }

    Big operator -(const Big &a)const{// 高精减 (大数减小数) 

        Big res;
        res.len=len;

        for(int i=1;i<=len;i++){
            res.num[i]+=num[i]-a.num[i];
            if(res.num[i]<0)res.num[i]+=P,res.num[i+1]--;
        }

        while(!res.num[res.len])res.len--;
        return res;
    }

    Big operator *(const Big &a)const{// 高精乘 

        Big res;

        for(int i=1;i<=len;i++){
            for(int j=1;j<=a.len;j++){
                res.num[i+j-1]+=(num[i]*a.num[j]);
                res.num[i+j]+=res.num[i+j-1]/P;
                res.num[i+j-1]%=P;
            }
        }

        res.len=len+a.len;
        if(res.num[res.len+1])res.len++;
        return res;

    }

    Big operator /(const int &a)const{// 高精除低精 
        Big res;
        for(int i=1;i<=len;i++)res.num[i]=num[i];
        res.len=len;
        for(int i=res.len;i>=1;i--){
            res.num[i-1]+=P*(res.num[i]%a);
            res.num[i]/=a;
        }
        while(!res.num[res.len])res.len--;
        return res;
    }

    void str_to_int(string a){// 字符串变成Bigint 
        len=a.length();
        for(int i=0;i<len;i++)num[len-i]=a[i]^'0';
    }

    void Add(){// 加1 
        num[1]++;
        for(int i=1;i<=len;i++)
            num[i+1]+=num[i]/P,num[i]%=P;
        if(num[len+1])len++;
    }

    void Dec(){// 减1 
        num[1]--;
        for(int i=1;i<=len;i++)
            if(num[i]<0)num[i]+=P,num[i+1]--;
        if(!num[len])len--;
    }

    void Print(){// 输出 
        for(int i=len;i>=1;i--)printf("%d",num[i]);
        puts("");
    }

    Big operator /(const Big &a)const{// 高精除 

        Big L=(/*下界*/),R=(/*上界*/),ans;
        while(!(R<L)){
            Big mid=(L+R)/2;
            Big tmp=mid*y;
            if(x<tmp)R=mid,R.Dec();
            else L=mid,L.Add(),ans=mid;;
        }

    }

};

这里既支持高精除高精,也支持高精除低精,其中的P为选择的进制,这里设为10,P的不同会导致str_to_int和Print两条函数设置的不同。

5、正向表:

struct node{
    int to,nxt;
}eg[M];

int tot,head[M];

void Add(int x,int y){
    tot++;
    eg[tot].to=y;
    eg[tot].nxt=head[x];
    head[x]=tot;
}

使用正向表的时候一定要计算好结构体数组的内存,否则炸了都不知道怎么回事。不过据说正向表会比vector快(STL因为全面性)。

三、常用算法

1、最短路之 dijkstra(优先队列优化):

struct node{
    int to,v;
    bool operator <(const node &a)const{
        return v>a.v;
    }
};

vector<node>v[M];
priority_queue<node>Q;
int dis[M];
bool mark[M];

int dijkstra(int s,int t){
    while(!Q.empty())Q.pop();
    memset(mark,0,sizeof(mark));
    memset(dis,63,sizeof(dis));
    dis[s]=0;
    Q.push((node){s,0});
    while(!Q.empty()){
        node now=Q.top();
        Q.pop();
        int x=now.to;
        if(mark[x])continue;
        if(x==t)return now.v;
        mark[x]=1;
        for(int i=0;i<v[x].size();i++){
            int y=v[x][i].to;
            if(mark[y])continue;
            if(dis[y]>dis[x]+v[x][i].v){
                dis[y]=dis[x]+v[x][i].v;
                Q.push((node){y,dis[y]});
            }
        }
    }
    return -1;
}

dijkstra是单源最短路算法,复杂度为 O(nlog2m)

2、最短路之Bellman_ford:

struct node{
    int u,v,cost;
}eg[M];
int m;//边数
int dis[M];
bool mark[M];
#define oo 1061109567
int Bellman_Ford(int s,int t){
    memset(dis,63,sizeof(dis));
    dis[s]=0;
    while(true){
        bool f=false;
        for(int i=1;i<=m;i++){
            node e=eg[i];
            if(dis[e.v]>dis[e.u]+e.cost){
                dis[e.v]=dis[e.u]+e.cost;
                f=true;
            }
        }
        if(f==false)break;
    }
    return dis[t]==oo?-1:dis[t];
}

复杂度目测上界是 O(mn) ,但是有时候跑的就是比dijkstra快很多。也是单源最短路,可以判负环。

3、最短路之SPFA:

struct node{
    int to,v;
};
vector<node>v[M];
int Q[M],dis[M];
bool mark[M];
#define oo 1061109567
int SPFA(int s,int t){
    int l=0,r=0;
    memset(dis,63,sizeof(dis));
    dis[s]=0;
    Q[r++]=s;
    mark[s]=1;
    while(l<r){
        int now=Q[l++];
        mark[now]=0;
        for(int i=0;i<v[now].size();i++){
            int p=v[now][i].to;
            if(dis[p]>dis[now]+v[now][i].v){
                dis[p]=dis[now]+v[now][i].v;
                if(!mark[p])Q[r++]=p,mark[p]=1;
            }
        }
    }
    return dis[t]==oo?-1:dis[t];
}

复杂度玄学,这里就不说了,也是单源的。

4、最短路之Floyd:

int eg[M][M];
int n,m,s,t;
#define oo 1061109567
int Floyd(int s,int to){
    for(int t=1;t<=n;t++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++)
                if(eg[i][j]>eg[i][t]+eg[t][j])eg[i][j]=eg[i][t]+eg[t][j];
        }
    }
    return eg[s][to]==oo?-1:eg[s][to];
}

复杂度 O(n3) ,但是是多源最短路,可以判断负环的存在。

5、最小生成树之kruskal:

struct node{
    int u,v,cost;
    bool operator <(const node &a)const{
        return cost<a.cost;
    }
}eg[M];

int Fa[M],n,m;
int Find(int x){
    if(Fa[x]==x)return x;
    return Fa[x]=Find(Fa[x]);
}
void Init(){
    sort(eg+1,eg+m+1);
    for(int i=1;i<=n;i++)Fa[i]=i;
}

int kruskal(){
    int res=0;
    for(int i=1;i<=m;i++){
        int fu=Find(eg[i].u);
        int fv=Find(eg[i].v);
        if(fu!=fv){
            Fa[fu]=fv;
            res+=eg[i].cost;
        }
    }
    return res;
}

kruskal是应用比较多的最小生成树算法,复杂度 O(mlog2m)

6、最小生成树之Prim:

struct node{
    int to,v;
    bool operator <(const node &a)const{
        return v>a.v;
    }
};
int n,m,dis[N],x,y,z,ans;
vector<node>e[M];
priority_queue<node>Q;
bool mark[N];
int dis[M];
int Prim(){
    int ans=0;
    memset(dis,63,sizeof(dis));
    dis[1]=0;
    Q.push((node){1,0});
    while(!Q.empty()){
        node now=Q.top();
        Q.pop();
        int k=now.to;
        if(mark[k])continue;
        ans+=now.v;
        mark[k]=1;
        for(int i=0;i<e[k].size();i++){
            int t=e[k][i].to;
            if(mark[t])continue;
            if(dis[t]>e[k][i].v){
                dis[t]=e[k][i].v;
                Q.push((node){t,dis[t]});
            }
        }
    }
    return ans;
}

与dijkstra算法有较大的相似处,只不过更正了小部分。复杂度与dijkstra也是一样的。

7、求LCA之倍增:

int Fa[S][N],dep[N];
vector<int>v[N];

void DFS(int now,int p,int d){
    Fa[0][now]=p;
    dep[now]=d;
    for(int i=0;i<v[now].size();i++){
        int t=v[now][i];
        if(t==p)continue;
        DFS(t,now,d+1);
    }
}

int n,m;

void Init(){

    for(int i=1;i<S;i++){
        for(int j=1;j<=n;j++){
            if(Fa[i-1][j]<0)Fa[i][j]=Fa[i-1][j];
            else Fa[i][j]=Fa[i-1][Fa[i-1][j]];
        }
    }

}

int LCA(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int step=dep[x]-dep[y];

    for(int i=S-1;i>=0;i--)
        if((1<<i)&step)x=Fa[i][x];

    if(x==y)return x;

    for(int i=S-1;i>=0;i--)
        if(Fa[i][x]!=Fa[i][y])x=Fa[i][x],y=Fa[i][y];

    return Fa[0][x];
}

复杂度为 O(nlog2n)

8、求LCA之树链剖分:

int Fa[N],dep[N],sz[N],Sn[N];
vector<int>v[N];

void P_DFS(int now,int p,int d){
    Fa[now]=p;
    dep[now]=d;
    sz[now]=1;
    for(int i=0;i<v[now].size();i++){
        int t=v[now][i];
        if(t==p)continue;
        P_DFS(t,now,d+1);
        sz[now]+=sz[t];
        if(sz[Sn[now]]<sz[t])Sn[now]=t;
    }
}

int top[N],szn;

void M_DFS(int now,int p,int tp){
    top[now]=tp;
    if(Sn[now])M_DFS(Sn[now],now,tp);
    for(int i=0;i<v[now].size();i++){
        int t=v[now][i];
        if(t==p||t==Sn[now])continue;
        M_DFS(t,now,t);
    }
}

void Init(){
    P_DFS(1,-1,0);
    M_DFS(1,-1,1);
}

int LCA(int x,int y){
    while(top[x]!=top[y]){
        int tx=top[x],ty=top[y];
        if(dep[tx]>dep[ty])x=Fa[tx];
        else y=Fa[ty];
    }
    return dep[x]>dep[y]?y:x;
}

树链剖分的复杂度应该也是 O(nlog2n) ,但不知道为什么就是比倍增要快一点。

四、 数学:

1、筛素数( O(n) ):

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

用于判断单个素数的话往往这种办法会比较好。

2、筛素数(?):

void Prime(int x){
    for(int i=2;i<M;i++){
        if(!mark[i]){
            for(int j=i+i;j<M;j+=i)
                mark[j]=1;
        }
    }
}

其中所有没被标记的点都是素数。这种办法可以筛出2到n的所有素数,不过复杂度比较玄学,据说是 O(nlog2log2n) 。两种筛法各有不同,在不同的情况灵活运用好了。

3、同余方程:

int Gcd(int a,int b,int &x,int &y){
    if(b==0){x=1;y=0;return a;}
    int res=Gcd(b,a%b,y,x);
    y-=a/b*x;
    return res;
}

同余方程一直是一个很奇怪的东西,反正我是没怎么记熟的。大概记住就好了。

4、欧拉函数(单个整数):

int phi(int x){
    int res=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            res=res/i*(i-1);
            while(x%i==0)x/=i;
        }
    }
    if(x>1)res=res/x*(x-1);
    return res;
}

复杂度和筛单个素数是一样的。

5、欧拉函数(筛区间):

int ph[M];

void Init(){
    for(int i=1;i<M;i++)ph[i]=i;
    for(int i=2;i<M;i++){
        if(ph[i]==i){//这是个素数 
            ph[i]=i-1; 
            for(int j=i+i;j<M;j+=i)
                ph[j]=ph[j]/i*(i-1);
        }
    }
}

这跟区间筛素数也是差不多的,复杂度也是那个比较魔性的函数。当然用法也是差不多的,看着耍吧。

6、快速幂:

int Fast(int x,int y,int P){//(x^y)%P
    int res=1;
    while(y){
        if(y&1)res=1ll*res*x%P;
        y>>=1;
        x=1ll*x*x%P;
    }
    return res;
}

快速幂在许多方面都会很有用。

7、拓展欧几里得:

int Gcd(int a,int b,int &x,int &y){//res为gcd(a,b).a,b,x,y满足a*x+b*y==1 (x为a%b的逆元)
    if(b==0){x=1,y=0;return a;}
    int res=Gcd(b,a%b,y,x);
    y-=a/b*x;
    return res;
}

拓展欧几里得也是比较有用的,在数论一方面是非常恶心的东西。

就这样吧,也不知道这里的模板会有多少能用到的。

以上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值