• OI 基础模板


    updated:  2021.2.3 新增线段树,树状数组。
    

    考试必备

    1.快速读入

    inline ll read()
    {
        int x=0,f=0;
        char ch=getchar();
        while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        return f?-x:x;
    }
    

    2.对拍

    3.卡常小技巧

    ① 位运算乘除

    [x=x imes2^n ~可以转化成~ x<<n ]

    [x=x÷2^n ~可以转化成~ x>>n ]

    ② for 卡常

    for(register int i(1); i<=n; ++i) {}
    

    ③ 短函数前加 inline 卡常

    ④ 判断奇偶

    if(a%2==0)  可以转化成  if((a&1)==0)
    

    ⑤ 取模用 &

    x=667%4;    可以转化成  x=667&(4-1);
    x=667%32;   可以转化成  x=667&(32-1);
    

    ⑥ 正负转换用位运算

    i=-1;       可以转换成  i=~i+1;   或   i=(i^-1)+1; 
    

    ⑦ 取绝对值

    k=abs(x);   可以转换成  k=(x^(x>>31))-(x >> 31);  
    

    数据结构

    1.ST表

    静态查询区间最值。

    ll f[100001][20];
    ll n,m,a[100001];
    
    void ST_make(){
        for(int i=1;i<=n;++i) f[i][0]=a[i];
        ll t=log(n)/log(2)+1;
        for(int j=1;j<t;++j)
            for(int i=1;i<=n-(1<<j)+1;++i)
                f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
    
    ll ST_q(ll l,ll r)
    {
        ll k=log(r-l+1)/log(2);
        return max(f[l][k],f[r-(1<<k)+1][k]);
    }
    
    int main()
    {
        n=read();
        m=read();
        for(int i=1;i<=n;++i)
        a[i]=read();
        ST_make();
        for(int i=1;i<=m;++i)
        {
            ll l=read(),r=read();
            printf("%lld
    ",ST_q(l,r));
        }
        return 0;
    }
    

    2.树状数组

    支持单点修改,区间查询。

    ll lowbit(ll x)
    {
        return x & (-x);
    }
    
    ll c[500002],n,m;
    
    void add(ll x,ll y) //单点修改
    {
        for(; x<=n;x+=lowbit(x)) c[x]+=y;
    }
    
    ll sum(ll x) //前缀和
    {
        ll ans=0;
        for(;x;x-=lowbit(x)) ans+=c[x];
        return ans;
    }
    
    ll ask(ll l,ll r) //区间查询
    {
        return sum(r)-sum(l-1);
    }
    
    int main()
    {
        n=read();
        m=read();
        for(int i=1;i<=n;++i) //初始化
        {
            ll x=read();
            add(i,x);
        }
        for(int i=1;i<=m;++i)
        {
            ll opt=read();
            if(opt==1) //单点修改
            {
                ll x=read(),k=read();
                add(x,k);
            }
            else if(opt==2) //区间查询
            {
                ll x,y;
                x=read();
                y=read();
                ll ans=ask(x,y);
                printf("%lld
    ",ans);
            }
        }
        return 0;
    }
    

    线段树

    注:本线段树使用链表形式(指针),每个结点都是左闭右开区间。

    单点修改,区间查询

    作用等同于树状数组,但是线段树时间和空间的开销都非常大。

    ll n, m;
    
    struct node
    { //树结点
        ll L, R, sum, k;
        node *lc, *rc;
    };
    
    void build(node *now, ll l, ll r) //建树
    {
        now->L = l;
        now->R = r;
        now->sum = 0;
        if (l + 1 < r) //不是叶子,继续递归
        {
            now->lc = new node;
            now->rc = new node;
            ll mid = (l + r) / 2;
            build(now->lc, l, mid);
            build(now->rc, mid, r);
        }
        else
            now->lc = now->rc = NULL; //是叶子,停下
    }
    
    ll check(node *now, ll l, ll r) //查询区间和
    {
        if (l <= now->L && now->R <= r + 1) //勿忘 r+1
            return now->sum;
        else
        {
            ll ans = 0;
            ll mid = (now->L + now->R) / 2;
            if (l < mid)
                ans += check(now->lc, l, r);
            if (r >= mid)
                ans += check(now->rc, l, r); //右边需要取等
            return ans;
        }
    }
    
    void change(node *now, ll x, ll k) //单点修改
    {
        if (now->L + 1 == now->R)
        {
            now->sum += k;
        }
        else
        {
            ll mid = (now->L + now->R) / 2;
            if (x < mid)
                change(now->lc, x, k);
            if (x >= mid)
                change(now->rc, x, k); //右边需要取等
            now->sum = now->lc->sum + now->rc->sum;
        }
    }
    
    int main()
    {
        n = read();
        m = read();
        node *root;
        root = new node; //创建根结点
        build(root, 1, n + 1);
    
        for (int i = 1; i <= n; ++i) //初始化
        {
            ll x = read();
            change(root, i, x);
        }
    
        for (int i = 1; i <= m; ++i)
        {
            ll opt = read();
            if (opt == 1) //区间修改
            {
                ll x = read(), k = read();
                change(root, x, k);
            }
            else if (opt == 2) //区间查询
            {
                ll l = read(), r = read();
                ll res = check(root, l, r);
                printf("%lld
    ", res);
            }
        }
        return 0;
    }
    

    区间修改,区间查询

    需要延迟修改。

    对于结点,新加 (k),代表延迟修改量。

    新增延迟信息下传函数 update()

    对于修改和查询函数新增延迟操作。

    ll n, m;
    
    struct node
    {                    //树结点
        ll L, R, sum, k; //多出了延迟修改量 k
        node *lc, *rc;
    };
    
    void build(node *now, ll l, ll r) //建树
    
    void update(node *now) //更新
    {
        now->lc->sum += now->k * (now->lc->R - now->lc->L);
        now->rc->sum += now->k * (now->rc->R - now->rc->L);
        now->lc->k += now->k;
        now->rc->k += now->k;
        now->k = 0;
    }
    
    ll check(node *now, ll l, ll r) //查询区间和
    {
        if (l <= now->L && now->R <= r + 1) //勿忘 r+1
            return now->sum;
        else
        {
            if (now->k != 0) //多了更新这一步
                update(now);
            ll ans = 0;
            ll mid = (now->L + now->R) / 2;
            if (l < mid)
                ans += check(now->lc, l, r);
            if (r >= mid)
                ans += check(now->rc, l, r); //右边需要取等
            return ans;
        }
    }
    
    void change(node *now, ll l, ll r, ll k) //区间修改
    {
        if (l <= now->L && now->R <= r + 1)
        {
            now->sum += k * (now->R - now->L);
            now->k += k;
        }
        else
        {
            if (now->k != 0) //同样多了更新这一步
                update(now);
    
            ll mid = (now->L + now->R) / 2;
            if (l < mid)
                change(now->lc, l, r, k);
            if (r >= mid)
                change(now->rc, l, r, k);
            now->sum = now->lc->sum + now->rc->sum;
        }
    }
    
    int main()
    {
        n = read();
        m = read();
        node *root;
        root = new node; //创建根结点
        build(root, 1, n + 1);
    
        for (int i = 1; i <= n; ++i) //初始化
        {
            ll x = read();
            change(root, i, i, x); //区间修改单点
        }
    
        for (int i = 1; i <= m; ++i)
        {
            ll opt = read();
            if (opt == 1) //区间修改
            {
                ll l = read(), r = read(), k = read();
                change(root, l, r, k);
            }
            else if (opt == 2) //区间查询
            {
                ll l = read(), r = read();
                ll res = check(root, l, r);
                printf("%lld
    ", res);
            }
        }
        return 0;
    }
    

    数学

    1.线性筛素数

    给定一个整数 (n) ,求出 $[2,n] $ 之间的所有素数。

    思路:prime 数组存放已经筛出的素数, (m) 代表素数个数(也就是说遍历时从 (1) 遍历到 (m) 即可),v 数组代表有没有被标记,避免重复筛。

    int v[maxn],prime[maxn],n,k,t,m;
    void primes(int n)
    {
        memset(v,0,sizeof(v));//清空标记数组
        m=0;//质数个数
        for(int i=2;i<=n;i++)
        {
            if(!v[i])//未被标记,i为质数
                v[i]=i,    prime[++m]=i; //记录
            for(int j=1;j<=m;j++)
            {
                if(prime[j]>v[i]||prime[j]>n/i) break;//i有更小的质因子,或者超出n的范围
                v[i*prime[j]]=prime[j];//prime[j]为合数 i*prime[j]的最小质因子
            }
        }
    }
    int main()
    {
        scanf("%d",&n);
        primes(n);
        for(int i=1;i<=m;++i)
            printf("%d
    ",prime[i]);
    }
    

    2.最大公约数 ((gcd))

    ① 标准

    inline int gcd(int a,int b) {
        int r;
        while(b>0) 
        {
            r=a%b;
            a=b;
            b=r;
        }
        return a;
    }
    

    ② 三目

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

    ③ 位运算

    inline int gcd(int a,int b) //a,b不能为0
    { 
        while(b^=a^=b^=a%=b);
        return a;
    }
    

    ④ 辗转相除法

    inline int gcd(int a,int b) 
    {
        if(a%b==0) return b;
            else return (gcd(b,a%b));
    }
    

    ⑤ 辗转相减法

    int gcd(int a,int b)
    {
        if(a==b)return a;
        return a>b?gcd(a-b,b):gcd(b-a,a);
    }
    

    ⑥ 外挂(考试禁止)

    #include <algorithm>
    inline int gcd(int a,int b) 
    {
        return __gcd(a,b); //其实可以在主函数里直接用这个
    }
    

    分治

    1.快速乘法取余

    给定三个整数 (a,n,mod) ,求 (a imes n ~\%~mod) 的值。

    inline int mult_mod(int a,int n,int mod)
    {
        int ans=0;
        while(n>0)
        {
            if(n&1) ans=(ans+a)%mod;
            a = (a+a)%mod;
            n >>= 1;
        }
        return ans;
    }
    

    这个东西好像没有必要的样子,貌似只需要 ((a~\%~mod)×(n~\%~mod)~\%~mod) 即可。

    2.快速幂(非递归)

    给定三个整数 (a,n,mod) ,求 (a^n~\%~mod) 的值。

    ll ksm(ll a,ll n,ll mod)
    {
        if(a==1 || n==0 ) 
        {
            if(mod==1) return 0;
            return 1;
        }
        ll m=a,ans=1;
        while(n>0)
        {
            if(n&1)
            {
                ans*=m;    
                ans%=mod;
            }
            m*=m;
            m%=mod;    
            n>>=1;
        }
        return ans;
    }
    

    3.最长上升子序列 (LIS)

    求一个序列的最长上升子序列个数。
    本程序采用边读边处理 + 二分法。

    ll f[maxn], ans = 1; //注意答案个数初始化为1
    
    int main()
    {
        ll n = read();
        for (int i = 1; i <= n; ++i)
        {
            int x = read();
            if (i == 1)
            {
                f[1] = x;
                continue;
            }
            int l = 1, r = ans, mid;
            while (l <= r)
            {
                mid = (l + r) >> 1;
                if (x <= f[mid])
                    r = mid - 1;
                else
                    l = mid + 1;
            }
            f[l] = x;
            if (l > ans)
                ++ans;
        }
        printf("%lld
    ", ans);
        return 0;
    }
    
    

    4. lower_bound

    使用前提:数列为有序数列。

    ①数组中二分查找

    //查找范围:[ begin , end ) ,左闭右开区间。 
    *lower_bound(begin,end,num);  //返回第一个 >= num 的数的数值
    lower_bound(begin,end,num)-begin; // 返回下标
    
    实际操作:
    int a[100]={0,1,3,5,7,9,10};
    //    下标:0 1 2 3 4 5  6
    int main()
    {
        int x=lower_bound(a+1,a+6+1,6)-a; //输出下标
        int y=*lower_bound(a+1,a+6+1,6);  //输出值
        printf("%d %d",x,y);
        return 0;
    }
    
    输出结果:4 7
    

    ②结构体内二分查找

    结构体内使用 lower_bound 需要重载,下面我们主要对结构体中的 (a) 进行操作。

    struct node //开结构体
    {
        int a, id; //定义结构体内的两个变量
        node() {}
        node(int x, int y) : a(x), id(y) {}
        bool operator < (const node t) const //重载
        {
            return a < t.a;
        }
    }t[1001];
    
    bool cmp(node x,node y) //快排 cmp 比较函数
    {
        if(x.a < y.a) return 1; //结构体内按 a 由小到大排序。
        return 0;
    }
    
    int main()
    {
        int n=read(); //数列中数的个数
        for(int i=1;i<=n;++i){
            t[i].a=read(); //读入数列
            t[i].id=i;
        }
        sort(t+1,t+n+1,cmp); //按小到大排序
    
        int x,xiabiao,ans;
        x=read(); //需要查找的数字
        xiabiao=lower_bound(t+1,t+n+1,node(x,0))-t; //这样求下标
        ans=(*lower_bound(t+1,t+n+1,node(x,0))).a;  //这样求值
        printf("%d %d
    ",xiabiao,ans);
        return 0;
    }
    
    输入:
    5
    20 40 30 10 50
    35
    输出:
    4 40
    

    另:upper_bound 的使用与 lower_bound 的使用类似,只不过是严格大于(>)。

    图论

    前置:链式前向星存图

    int head[maxn], dis[maxn];
    bool vis[maxn];
    struct node
    {
        ll nxt, to, w;
    }t[maxn];
    void add (const int u,const int v,const int w)
    {
        t[++tot].to = v;
        t[tot].w = w;
        t[tot].nxt = head[u];
        head[u] = tot;
    }
    

    1.Dijkstra 最短路

    求单源 (s) 到任意一点的最短路径。最短路径保存在数组 dis 中。

    #include <queue>
    priority_queue <pair <ll, ll> > q;
    void dijkstra(int s)
    {
        memset (dis, 0x3f3f3f3f, sizeof (dis));      //初始边无限大
        memset (vis, 0, sizeof (vis));        //结点初始均为访问 
        dis[s] = 0;    //起点到自己距离为0 
        q.push (make_pair (0, s));    //起点进队 
        while (q.size() != 0)
        {
            x = q.top().second;
            q.pop();    //初始结点入队  
            if (vis[x])  
                continue;    //如果走过,直接跳过 
            vis[x] = 1;    //标记已访问 
            for (ll i = head[x]; i!=-1; i = t[i].nxt)
            {
                ll y = t[i].to, z = t[i].w;
                if (dis[y] > dis[x] + z)
                {
                    dis[y] = dis[x] + z;    //更新起点到y最短路 
                    q.push (make_pair (-dis[y], y));    //d[y]相反数入队,转小根堆
                }
            }
        }
    }
    int main()
        for(int i=1;i<=n;++i)    head[i]=-1;
        //后面省略
    

    2.SPFA 最短路&最长路&判断负环

    SPFA能处理负边权,可以判断负环。也可以求最长路。

    ①最短路

    #include<queue>
    queue<int> q;
    void SPFA(int s)
    {
        fill(dis+1,dis+1+n,2147483647);//初始边无限大    
        memset(vis,0,sizeof vis);
        dis[s]=0;
        q.push(s);
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            vis[x]=0;
            for(int i=head[x];i!=-1;i=t[i].nxt)
            {
                int y=t[i].to,z=t[i].w;
                if(dis[y]>dis[x]+z)
                {
                    dis[y]=dis[x]+z;
                    if(vis[y]==0)
                    {
                        q.push(y);
                        vis[y]=1;
                    }
                }
            }
        }
    }
    

    ②最长路

    可依据最短路代码进行修改。

    1. `dis` 数组赋初值时,如果没有负边权就赋 $-1$ ,如果有负边权就赋无限小。
    
    1. dis[y]>dis[x]+z 中的 > 改成 <

    ③判断负环

    可在最短路代码基础上进行修改。需新加入一个数组 cnt ,专门记录负环。

    补充代码:

    ll cnt[maxn];//专门记录负环
    void SPFA().....
    if(dis[y]>dis[x]+z)
    {
        dis[y]=dis[x]+z;
        cnt[y]++;
        if(cnt[y]>=n+1)//出现超过n次表示就有负环 
        {
            ans=1; //ans=1代表有负环。
            return;
        } 
        if(vis[y]==0)
        {
            q.push(y);
            vis[y]=1;
        }
    }
    

    3.Floyd 全源最短路

    inline void floyd()
    {
        for(k=1;k<=n;k++)  
            for(i=1;i<=n;i++)  
                for(j=1;j<=n;j++)  
                    if(e[i][j]>e[i][k]+e[k][j])   
                        e[i][j]=e[i][k]+e[k][j];  
    }
    

    4.并查集

    (n) 代表元素个数,(m) 为操作数。

    (opt=1) 时,合并集合 (a,b)(opt=2) 时,如果 (a,b) 在同一集合,输出 Y 否则输出 N

    int find(int k)
    {
        if(f[k]==k)return k;
        return f[k]=find(f[k]);
    }
    
    int main()
    {
        n=read();
        m=read();
        for(int i=1;i<=n;++i) f[i]=i; //初始化自己的老大是自己
         
        for(int i=1;i<=m;++i)
        {
            int opt,a,b;
            opt=read();
            a=read();  b=read();
            if(opt==1) 
                f[find(a)]=find(b);
            else
            {
                if(find(a)==find(b))
                printf("Y
    ");
                else printf("N
    ");
            }
        }
        return 0;
    }
    

    5.Prim 最小生成树

    int ans,cnt,now=1;//Prim
    void prim()
    {
        for(int i=2;i<=n;++i)
            dis[i]=MAXN;
            
        for(int i=head[1];i;i=t[i].nxt)
            dis[t[i].to] = min(dis[t[i].to],t[i].w);
        
        while(++cnt<n)
        {
            int minn=MAXN;
            vis[now]=1;
            for(int i=1;i<=n;++i)
            {
                if(vis[i]) continue;
                if(minn > dis[i])
                {
                    minn=dis[i];
                    now=i;
                }
            } 
            
            ans+=minn;
            
            for(int i=head[now];i;i=t[i].nxt)
            {
                int y=t[i].to, z=t[i].w;
            if(vis[y]) continue;
                if(dis[y] > z)
                {
                    dis[y]=z;
                }
            }
        }
    }
    

    6.拓扑排序

    ll ans[100],cnt; //拓扑序列及其元素个数
    ll deg[100]; //所有点的入度
    
    void topsort()
    {
        queue<ll> q;
        for(int i=1; i<=n; ++i)
            if(deg[i]==0) //寻找最开始入度就为0的点
                q.push(i); //入队
    
        while(!q.empty())
        {
            ll x=q.front(); q.pop();
            ans[++cnt]=x; //把队首的元素放进拓扑序列
            for(int i=head[x]; i; i=t[i].nxt)
            {
                ll y=t[i].to; //寻找邻边
                if(--deg[y]==0) //邻边的入度-1,并且判断减后的入度是否为0
                    q.push(y); //如果为0就入队
            }
        }
    }
    
    int main()
    {
        n=read();m=read(); //点,边
        for(int i=1;i<=m;++i)
        {
            ll x=read(),y=read();
            add(x,y);
            deg[v]++; //入度++
        }
    
        topsort(); //拓扑排序
    
        if(cnt<n) //拓扑序列的元素个数小于点数,说明有环
            puts("有环");
        else puts("无环");
    
        for(int i=1;i<=cnt;++i)
            printf("%lld ",ans[i]); //输出拓扑序列
        
        return 0;
    }
    

    常用 STL 初步

    1.队列(先进先出)

    #include<queue>
    queue<int> q; //priority_queue<int> q(从大到小排序);
    q.empty();  //判断队列是否为空
    q.size();   //返回队列长度
    q.push(item);   //对于queue,在队尾压入一个新元素
                    //对于priority_queue,在基于优先级的适当位置插入新元素
    q.pop();    //弹出队首元素
    
    //queue only:
    q.front();  //返回队首元素的值,但不删除该元素
    q.back();   //返回队尾元素的值,但不删除该元素
         
    //priority_queue only:
    q.top();    //返回具有最高优先级的元素值,但不删除该元素
    

    2.栈(先进后出)

    #include<set>
    stack<int> s;
    stack< int, vector<int> > stk;  //覆盖基础容器类型,使用vector实现stk
    s.empty();  //判断stack是否为空,为空返回true,否则返回false
    s.size();   //返回stack中元素的个数
    s.pop();    //删除栈顶元素,但不返回其值
    s.top();    //返回栈顶元素的值,但不删除此元素
    s.push(item);   //在栈顶压入新元素item
    

    3.set(自动从小到大排序,自动去重)

    set<int> s;//multiset<int> s (不去重)
    set<int>::const_iterator iter; //迭代器 
    
    s.insert();   //插入
    s.erase();    //若参数为元素值,则删除所有该元素值
                  //若参数为迭代器,则删除该迭代器指向的值
    s.empty();    //判断set是否为空,为空返回 true,否则返回 false
    s.count();    //返回某个值元素的个数
    s.clear();    //清除所有元素
    s.find();     //查找某元素,找到则返回其迭代器,否则返回 s.end()
    s.begin();    //返回指向第一个元素的迭代器
    --s.end();    //返回指向最后一个元素的迭代器
    *s.begin();   //返回指向第一个元素的值
    *--s.end();   //返回指向最后一个元素的值
                  //区间形式为 [ begin , end ) ,所以 end 要自减
    s.size();     //返回集合中元素的个数
    *s.lower_bound(k);    //返回第一个大于等于k的元素值
    *s.upper_bound(k);    //返回第一个大于k的元素值 (后继)
                  //如果没有符合条件的值,则输出 s.size()
    
    /* 遍历 */
    for(iter = s.begin() ; iter != s.end() ; ++iter)
    {
        cout<<*iter<<" ";//使用迭代器遍历 
    }
    

    4.map

    #include<map>
    map<string,int> m;//string 是 key,int 是 value。
    m.size();   //输出元素个数
    m.empty();  //如果 map 为空则返回 true
    m.clear();  //删除所有元素
    ......
    
    m["AKIOI"]=10;
    cout<<m["AKIOI"];
    输出结果:10
    

    · unordered_map

    #include<unordered_map>
    用法:与 map 差别不大。
    优点:因为内部实现了哈希表,因此其查找速度非常的快
    缺点:哈希表的建立比较耗费时间
    适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用 unordered_map
    

    EdisonBa

    2020.11.6 初次编辑

    2020.12.1 重大修改

  • 相关阅读:
    Jenkins 完成安装环境配置
    Jenkins中文社区的所有镜像地址
    VueX源码分析(3)
    VueX源码分析(2)
    VueX源码分析(1)
    Element表单验证(2)
    Element表单验证(1)
    配置淘宝镜像,不使用怪异的cnpm
    React动态import()
    cnpm 莫名奇妙bug 莫名奇妙的痛
  • 原文地址:https://www.cnblogs.com/EdisonBa/p/OI.html
Copyright © 2020-2023  润新知