NOIP复赛的各种模板

一:文件读入输出

freopen("Add.in","r",stdin);
freopen("Add.out","w",stdout);

就是类似于这种,比如一道题目的文件名为Add,那么就可以通过这样的格式来完成读入以及输出.

二:读入输出挂

void Rd(int &res){
    res=0;char p;
    while(p=getchar(),p<'0');
    do{
        res=(res<<1)+(res<<3)+(p^48);
    }while(p=getchar(),p>='0');
}
void Pt(int x){
    if(x==0)return;
    Pt(x/10);
    putchar(x%10^48);
}
void Ps(int x){
    if(x<0)putchar('-'),x=-x;
    if(x==0)putchar('0');
    else Pt(x);
}

就是逐个字符地读入数据,从而让读入更加快速.

输出挂的原理也是一样的,都是通过将输出数字变成输出字符以加快速度(都是黑科技).

如果要读入负数,要在读入挂中加入一些东西.

void Rd(int &res){
    res=0;char p;int k=1;
    while(p=getchar(),!(p>='0'&&p<='9')&&p!='-');
    if(p=='-')k=-1,p=getchar();
    do{
        res=(res<<1)+(res<<3)+(p^48);
    }while(p=getchar(),(p>='0'&&p<='9'));
    res*=k;
}

就是在读入中也要判断一下是否已经读到了负号.

三:数据结构

数据结构是这门竞赛的很重要的一部分吧,很多东西的实现都要靠数据结构才能够得到很好的优化(就像在一些dp题中把 O(n2) 优化到 O(nlogn) ),从而完成很多题目.有些题目甚至是想到某些数据结构就能够直接写出来了.

1.并查集:

int fa[M];
void Init(){
  //将所有元素的father指向自己
    for(int i=1;i<=n;i++)
        fa[i]=i;
}
int getfa(int x){
  //找到某个元素当前的father,同时进行路径压缩
    return x==fa[x]?x:fa[x]=getfa(fa[x]);
}

并查集的话,主要的东西就只有这一点了.

就是每个元素都会指向一个father,然后每个集合内的元素都会指向同一个元素.

然后合并两个集合的时候,就只需要找到这两个集合中的各一个元素,然后使用getfa函数找到他们两个集合的根,把其中的一个根指向另外一个根就好了.

并查集的话,可以快速地合并两个集合,以及判断两个集合是否连通,在一些题目中可以很有效地使用(比如当向一张图中加边时,可以用并查集来判断两个点之间有没有一条路径相连).

2.树状数组

struct Bit{
    int Tree[M];
    void Add(int x,int v){
        while(x<=n){
            Tree[x]+=v;
            x+=x&(-x);
        }
    }
    int Sum(int x){
        int re=0;
        while(x){
            re+=Tree[x];
            x-=x&(-x);
        }
        return re;
    }
}T;

haha
就是类似于这样的数据结构,能够支持在某一个地方加上一个数,然后支持询问前缀和.

x&(-x)是得到x的二进制表示的最后一个1(也就是lowbit).

然后这样的原理就和上面的那一张图很像,就是每个询问会找到一些二进制位所对应的块,然后把这些块里的信息收集上来,就完成了一次询问.

然后更新的话,要把上面那张图的对应数字上的块都更新一下,就可以用x+=x&(-x)来做到了.

然后如果只用维护1到n的关系的话,也可以用树状数组,比如要求1到n的最大值什么的.

3.线段树

struct SegmentTree{
    int Tree[M<<2];
    void build(int L,int R,int p){
  //L,R是线段树这个节点的两个端点,p是这个节点的编号. 
        Tree[p]=0;
        if(L==R)return;
        int mid=L+R>>1;
        build(L,mid,p<<1);
        build(mid+1,R,p<<1|1);
    }
    void update(int L,int R,int p,int x,int v){
  //把x这个点改成v 
        if(L==R){
            Tree[x]=x;
            return;
        }
        int mid=L+R>>1;
        if(mid>=x)update(L,mid,p<<1,x,v);
        else update(mid+1,R,p<<1|1,x,v);
        Tree[p]=max(Tree[p<<1],Tree[p<<1|1]);
    }
    int query(int L,int R,int p,int l,int r){
  //查询[l,r]的最大值 
        if(L==l&&R==r)return Tree[p];
        int mid=L+R>>1;
        if(mid>=r)return query(L,mid,p<<1,l,r);
        else if(mid<l)return query(mid+1,R,p<<1|1,l,r);
        else return max(query(L,mid,p<<1,l,mid),query(mid+1,R,p<<1|1,mid+1,r));
    }
}T;

这样的话,就是一个支持单点更新,区间查询最大值的一个线段树了.

线段树其实是树状数组的升级版吧(我觉得),虽然常数什么的更大一些,但是能够做的事情更多,比如它可以只求一个区间内的极值,和之类的,而不是1到x的极值以及和.

所以说线段树能够做的事情更多,但是它用的空间也更大一些,但是无伤大雅啊.只要内存开的下就好了.

但是线段树的话还可以支持区间更新或者单点查询什么的,只要在代码里改一下就好了.

//区间更新,以将区间一段数加上一个相同的数为例吧,然后就是求区间的和.
void update(int L,int R,int p,int l,int r,int v){
        if(L==l&&R==r){
            Tree[p]+=v*(r-l+1);
            Add[p]+=v;
            return;
        }
        int mid=L+R>>1;
        if(Add[p]!=0){
            Add[p<<1]+=Add[p];
            Add[p<<1|1]+=Add[p];
            Tree[p<<1]+=Add[p]*(mid-l+1);
            Tree[p<<1|1]+=Add[p]*(r-mid);
            Add[p]=0;
        }
        if(mid>=r)update(L,mid,p<<1,l,r,v);
        else if(mid<l)update(mid+1,R,p<<1|1,l,r,v);
        else update(L,mid,p<<1,l,mid,v),update(mid+1,R,p<<1|1,mid+1,r,v);
        Tree[p]=Tree[p<<1]+Tree[p<<1|1];
    }

单点查询什么的还是不打了吧…

4.st表

struct StTable{
    int val[M],Mx[LOGM][M],Log[M];
    void Init(){
        Log[0]=-1;
        for(int i=1;i<M;i++)Log[i]=Log[i>>1]+1;
        for(int i=1;i<=n;i++)Mx[0][i]=val[i];
        for(int i=0;i+1<LOGM;i++)
            for(int j=1;j<=n;j++)
                if(j+(1<<i)<=n)Mx[i+1][j]=max(Mx[i][j],Mx[i][j+(1<<i)]);
                else Mx[i+1][j]=Mx[i][j];
    }
    int Query(int l,int r){
        int len=r-l+1;
        int Lg=Log[len];
        return max(Mx[Lg][l],Mx[Lg][r-(1<<Lg)+1]);
    }
}St;

差不多就是这样的,就是通过 O(nlogn) 的预处理,然后做到 O(1) 地查询一个区间内的最小值.

就是和倍增一样的思想,先预先处理出每个数字向后再走 2n 个数的最大值,然后对于一个[l,r]的询问,我们先处理出一个k,使得 2k+1>=(rl+1) ,然后对于两个大小为 2k 的区间,这样的区间就能够覆盖整个区间了,然后就可以做到 O(1) 的进行查询区间极值.

5.正向表

struct Li{
    int to,co,nx;
}Lis[M];
int Head[M],tot=0;
void Add(int x,int y,int z){
  //在x,y之间 连一条边权为z的边 
    Lis[tot].to=y,Lis[tot].co=z,Lis[tot].nx=Head[x],Head[x]=tot++;
    Lis[tot].to=x,Lis[tot].co=z,Lis[tot].nx=Head[y],Head[y]=tot++;
}

就是开一个内存池,然后把所有边都储存在里面,因为如果用vector的话,因为要维护下标,所以在运行速度上会有一些偏差,这时候就能够用正向表来加快速度(骗分利器).

然后遍历的话就是这样的:

for(int i=Head[x];~i;i=Lis[i].nx)//遍历 

6.高精度

struct Big{
    #define P 10000
    int num[1005],len;
    Big(){
        memset(num,0,sizeof(num));
        len=1;
    }
    void Rd(){
        scanf("%s",A);
        int stlen=strlen(A);
        len=0;
        for(int i=stlen-1,res;res=0,i>=0;num[len++]=res,i-=4)
            if(i>=3){
                for(int j=i-3;j<=i;j++)res=(res<<1)+(res<<3)+(A[j]^48);
            }else {
                for(int j=0;j<=i;j++)res=(res<<1)+(res<<3)+(A[j]^48);
            }
    }
    bool operator <(const Big &a)const{
        if(len!=a.len)return len<a.len;
        for(int i=len-1;i>=0;i--)
            if
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值