常数优化的一些技巧

一.STL

原则上手写要比用STL快,不过有些确实难打···

map和set直接用就好,都是基于红黑树实现(根本不会打),效率已经足够高(当然不惧码量的巨佬也可以手打)

#include<cstdio>
#include<cstdlib>
using namespace std;
#define L tree[x].l
#define R tree[x].r
int const N=2e5+5;
int root,n,opt,st,t;
struct Treap{
    int l,r,id,weight,size;
}tree[N];
inline int apply(int x){
    int k=++t;
    tree[k].id=x,tree[k].weight=rand();
    tree[k].size=1;
    return k;
}
inline void get(int x){
    tree[x].size=tree[L].size+tree[R].size+1;
    return ;
}
void split(int x,int val,int &a,int &b){
    if(!x){
        a=b=0;
        return ;
    }
    if(tree[x].id<=val){
        a=x;
        split(R,val,R,b);
    }
    else{
        b=x;
        split(L,val,a,L);
    }
    get(x);
    return ;
}
int merge(int x,int y){
    if(!x || !y)return x+y;
    if(tree[x].weight<tree[y].weight){
        R=merge(R,y);
        get(x);
        return x;
    }
    tree[y].l=merge(x,tree[y].l);
    get(y);
    return y;
}
void insert(int x){
    int a,b;
    split(root,x,a,b);
    root=merge(merge(a,apply(x)),b);
    return ;
}
void del(int y){
    int a,b,x;
    split(root,y,a,b);
    split(a,y-1,a,x);
    root=merge(merge(a,merge(L,R)),b);
    return ;
}
int rk(int x){
    int a,b,ans;
    split(root,x-1,a,b);
    ans=tree[a].size+1;
    root=merge(a,b);
    return ans;
}
int find(int x,int y){
    while(tree[L].size+1!=y)
        if(y<=tree[L].size)x=L;
        else y-=tree[L].size+1,x=R;
    return tree[x].id;
}
int pre(int x){
    int a,b,ans;
    split(root,x-1,a,b);
    ans=find(a,tree[a].size),root=merge(a,b);
    return ans;
}
int nxt(int x){
    int a,b,ans;
    split(root,x,a,b);
    ans=find(b,1),root=merge(a,b);
    return ans;
}
int main(){
    scanf("%d",&n);
    while(n--){
        scanf("%d%d",&opt,&st);
        switch(opt){
            case 1:
                insert(st);
                break;
            case 2:
                del(st);
                break;
            case 3:
                printf("%d\n",rk(st));
                break;
            case 4:
                printf("%d\n",find(root,st));
                break;
            case 5:
                printf("%d\n",pre(st));
                break;
            case 6:
                printf("%d\n",nxt(st));
                break;
        }
    }
        
    return 0;
}
为证明自己是不惧码量的巨佬强行肝的一棵不是红黑树的普通平衡树

map确实很方便,不过有一种神奇的东西叫做hash表,可以以近似$\Theta(1)$的效率进行查询^_^(就是有点不稳定……)

#include<cstdio>
using namespace std;
int const mod=7e5+1,N=1e5+5;
int head[mod+2],Next[N],to[N],t;
inline int up(int x){
    return x<0?x+mod:x;
}
inline int ins(int x){
    int z=up(x%mod);
    for(register int i=head[z];i;i=Next[i])
        if(to[i]==x)return i;
    Next[++t]=head[z],head[z]=t;
    to[t]=x;
    return t;
}
int main(){
    int n;
    scanf("%d",&n);
    for(register int i=1;i<=n;++i){
        int x;
        scanf("%d",&x);
        printf("%d\n",ins(x));//返回离散后对应的值
    }
    return 0;
}
View Code

堆(优先队列)可以考虑手写,不过大部分情况直接用就行

但手写堆也有好处,就是快啊可以删除堆中的元素

#include<cstdio>
#include<cstring>
using namespace std;
int const N=1e5+5;
int heap[N],n;
inline void swap(int &x,int &y){
    x^=y^=x^=y;
    return ;
}
struct node{                    //大根堆
    int heap[N],n;
    inline void clear(){        //清空
        n=0;
        memset(heap,0,sizeof(heap));
        return ;
    }
    inline bool empty(){        //判断是否为空
        return !n;
    }
    inline int size(){            //返回元素个数
        return n;
    }
    inline void up(int x){        //向上调整
        while(x^1)
            if(heap[x]>heap[x>>1])swap(heap[x],heap[x>>1]),x>>=1;
            else return ;
    }
    inline void down(int x){    //向下调整
        int s=x<<1;
        while(s<=n){
            if(s<n && heap[s]<heap[s|1])s|=1;
            if(heap[s]>heap[x]){swap(heap[s],heap[x]);x=s,s<<=1;}
            else return ;
        }
    }
    inline void push(int x){    //插入元素x
        heap[++n]=x;
        up(n);
        return ;
    }
    inline int top(){return heap[1];}                        //返回堆中的最大值
    inline void pop(){heap[1]=heap[n--];down(1);return ;}   //删除堆顶
    inline void erase(int x){                                //删除下标为x的节点
        heap[x]=heap[n--];
        up(x),down(x);
        return ;
    }
    inline int* begin(){                //返回堆中第一个元素的指针(实在不会搞迭代器……)
        return &heap[1];
    }
    inline int* end(){                    //返回堆的尾部边界
        return &heap[n+1];
    }
    inline int &operator [] (int x){
        return heap[x];
    }
}q;
int main(){
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    int t;
    scanf("%d",&t);
    for(register int i=1;i<=t;++i){
        int z;
        scanf("%d",&z);
        q.push(z);
    }
    for(register int* i=q.begin();i!=q.end();++i)    //遍历1
        printf("%d ",*i);
    for(register int i=1;i<=q.size();++i)            //遍历2
        printf("%d\n",q[i]);
    while(!q.empty()){                                //从大到小输出
        printf("%d ",q.top());
        q.pop();
    }
    return 0;
}
View Code

至于stack和queue,必须手写!!!

注意stack打法,do-while循环更加方便

#include<iostream>
using namespace std;
int stack[1000],top;//
int q[1000],t,u;    //队列
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    for(register int i=1;i<=n;++i){
        cin>>q[++t];
        stack[top++]=q[t];
    }
    do{
        cout<<stack[--top]<<" ";
    }while(top);
    cout<<endl;
    while(u^t)cout<<q[++u]<<" ";
    return 0;
}
View Code

vector其实不是很快,有个题我没用vecotr但比用vector的多个sort,结果比开vector的快1倍(虽然我二分手打sort随机化还加了fread)

所以内存允许的话直接开2维数组(但vector确实挺方便,也不能总牺牲码量优化时间吧,所以想用就用)

pair也挺好,不过自定义结构体更快

总之,c++内置的改成手打一定变快,除非你打错了……

二.运算

mod定义成const

能乘不除,能加减别用魔法模法

能位运算就别用加减乘除···

x2^n改成<<n

/2^n改成>>n

swap(x,y)改成x^=y^=x^=y

模数若为2^n可以直接&(mod-1)

也可以先开unsigned int最后取模

两个小于模数相加用down(x)

(x%mod+mod)%mod改成up(x%mod)

inline int down(int x){
    return x<mod?x:x-mod;
}
inline int up(int x){
    return x<0?x+mod:x;
}
View Code

数据范围不大可以开long long,中间不取模最后再取

判断奇偶&1

i!=-1改为~i

!=直接改成^

三.读入

别用cin,用cin就在主函数加:

ios::sync_with_stdio(false);

cin.tie(0);

打着还麻烦,所以就用scanf或快读

不超过1e5的数据scanf即可

再大了最好用快读

记得位运算优化···

还嫌慢上fread(不过这个玩意有时候还会有意想不到的奇效,比如让你的程序慢十倍,慎用)

快读还有一些妙用

比如定义常量不能scanf但可用快读赋值

这在一些读取模数的题目中很有用

下面代码里快读读的是非负整数,读整数特判一下有无‘-’即可,就不给出实现了

#include<cstdio>
#include<iostream>
using namespace std;
int const L=1<<20|1;
char buf[L],*S,*T;
#define getchar() ((S==T&&(T=(S=buf)+fread(buf,1,L,stdin),S==T))?EOF:*S++)
inline int read(){
    int ss=0;char bb=getchar();
    while(bb<48||bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    n=read();
    puts("233");
    return 0;
}
View Code

四.输出

同理,用printf别用cout

快输是个危险东西,搞不好还会变慢

慎用非递归版快输,输不了零

不过非递归快一点,实在不行特判~

#include<cstdio>
#include<iostream>
using namespace std;
char ch[20],top;
inline void write(int x){        //非递归 
    while(x){
        ch[top++]=x%10;
        x=x/10;
    }
    do{
        putchar(ch[--top]+48);
    }while(top);
    puts("");
    return ;
}
void out(int x){                //递归 
    if(x>=10)out(x/10);
    putchar(x%10+48);
    return ;
}
int main(){
    int n=233;
    write(n);
    out(n);
    return 0;
}
View Code

ps. skyh大神表示非递归版改成do-while循环就能输出0了,%%%

#include<bits/stdc++.h>
using namespace std;
char ch[100],top;
inline void write(int x){
    do{
        ch[top++]=x%10;
        x/=10;
    }while(x);
    do{
        putchar(ch[--top]+48);
    }while(top);
    return ;
}
signed main(){
    write(5);write(2);write(0);
    return 0;
}
View Code

五.dp

其实已经不算卡常了,可以说是剪枝···

1.排除冗杂

能不dp的就别dp

说白了就是for循环里设个限制条件

比如可怜与超市一题

yzh巨佬重设了个tmp数组实现$\Theta(N^2)$转移,还证明了一波复杂度,%%%

但其实$\Theta(N^3)$可过···

#include<cstdio>
#include<cstring>
using namespace std;
int const N=5005,lar=0x3f3f3f3f,L=1<<20|1;
char buf[L],*S,*T;
#define getchar() ((S==T&&(T=(S=buf)+fread(buf,1,L,stdin),S==T))?EOF:*S++)
inline int read(){
    int ss=0;char bb=getchar();
    while(bb<48 || bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
inline void swap(int &x,int &y){
    int z=x;
    x=y,y=z;
    return ;
}
inline int max(int x,int y){
    return x>y?x:y;
}
inline int min(int x,int y){
    return x<y?x:y;
}
int n,m,pp;
int c[N],d[N],f[N][N][2];
int head[N],Next[N],to[N],t;
int siz[N],lim[N];
inline void add(int x,int y){
    to[++t]=y;
    Next[t]=head[x],head[x]=t;
    return ;
}
void dfs(int x){
    int y,now=2;
    siz[x]=1;
    f[x][1][0]=c[x];
    f[x][1][1]=c[x]-d[x];
    for(int i=head[x];i;i=Next[i]){
        dfs(y=to[i]);
        siz[x]+=siz[y=to[i]];
        for(register int j=siz[x];j>=0;--j){
            int lit=min(now,j);
            for(register int k=(j>lim[y])?j-lim[y]:1;k<lit;++k){
                int o=j-k;
                f[x][j][0]=min(f[x][j][0],f[y][o][0]+f[x][k][0]);
                f[x][j][1]=min(f[x][j][1],min(f[y][o][0],f[y][o][1])+f[x][k][1]);
            }
            f[x][j][0]=min(f[x][j][0],f[y][j][0]);
        }
        for(register int j=siz[x];j>=0;--j)
            if(f[x][j][0]<=m || f[x][j][1]<=m){now=j+1;break;}
    }
    for(register int i=1;i<=siz[x];++i)
        if(f[x][i][1]>=m && f[x][i][0]>=m){lim[x]=i;return ;}
    lim[x]=siz[x];
    return ;
}
int main(){
    memset(f,0x3f,sizeof(f));
    n=read(),m=read(),c[1]=read(),d[1]=read();
    for(register int i=2;i<=n;++i){
        c[i]=read(),d[i]=read();
        add(read(),i);
    }
    dfs(1);
    for(register int i=lim[1];i>=0;--i)
        if(f[1][i][0]<=m || f[1][i][1]<=m){
            printf("%d",i);
            return 0;
        }
}
View Code

2.等效替代

说起来很模糊···

以HAOI2015树上染色为例

染黑点和染白点其实一样

所以你完全可以加一句k=min(k,n-k);

六.初始化

单个变量可以直接初始化,好像比赋值初始化略快

小范围初始化数组直接memset

对于单一数组memset就是比for循环要快,不要怀疑!!!

有时后你觉得for循环快,那不是因为数据水与极限数据相差太远就是因为你连清了五个以上数组。

清大量范围相同的数组才采用for

对于一些题目你觉得memset用sizeof(数组名)清数组很浪费也可以改成sizeof(int)*长度,不过一般没有必要

当然一些情况你完全可以边for边初始化

最典型的就是矩阵乘法

struct ljj{
    int a[101][101];
    friend ljj operator * (ljj a1,ljj a2){
        ljj c;
        for(register int i=1;i<=100;++i)
            for(register int j=1;j<=100;++j){
                c.a[i][j]=0;
                for(register int k=1;k<=100;++k)
                    c.a[i][j]+=a1.a[i][k]*a2.a[k][j];
            }
    }
};
View Code

七.排序

动态维护的用堆或者平衡树

静态可以sort,归并并不推荐(主要是我不会···)

当然一些算法如CDQ可以边分治边归并的就别sort了

sort结构体时注意最好重载运算符,定义比较函数比较慢

值域小的桶排序

关于sort还有一个神奇操作

叫做随机化快排

大量用sort且待排序的数比较多得话可以考虑一下,因为直接用库函数容易栈溢出

顺带一提,随机化快排对有序数列的排序比普通快排快上个几百倍

说白了就是随机化快排不容易被特殊数据卡

再说白了就是能多骗点分…

#include<bits/stdc++.h>
using namespace std;
int a[1000001];
int random_div(int *q,int l,int r){
    int z=l+rand()%(r-l+1),zl=l-1,tp;
    swap(q[z],q[r]),tp=q[r];
    for(register int i=l;i<r;++i)
        if(q[i]<=tp)
            ++zl,swap(q[zl],q[i]);
    swap(q[++zl],q[r]);
    return zl;
}
void Random_Sort(int *q,int l,int r){
    if(l<r){
        int z=random_div(q,l,r);
        Random_Sort(q,l,z-1);
        Random_Sort(q,z+1,r);
    }
    return ;
}
int ran(int x){
    return (long long)rand()*rand()%x;
}
int main(){
    srand(time(NULL));
    int n;
    scanf("%d",&n);
    for(register int i=1;i<=n;++i)scanf("%d",&a[i]);
    Random_Sort(a,1,n);
    for(register int i=1;i<=n;++i)
        printf("%d ",a[i]);
    puts("");
    return 0;
}
View Code

当然实在不会打(比如我)只在sort前加个random_shuffle有时候也会让sort飞快(也有可能让你T飞)

八.头文件

慎用!!!!!!!!

#pragma GCC optimize(2)
#pragma GCC optimize(3) #pragma GCC optimize("Ofast") #pragma GCC optimize("inline") #pragma GCC optimize("-fgcse") #pragma GCC optimize("-fgcse-lm") #pragma GCC optimize("-fipa-sra") #pragma GCC optimize("-ftree-pre") #pragma GCC optimize("-ftree-vrp") #pragma GCC optimize("-fpeephole2") #pragma GCC optimize("-ffast-math") #pragma GCC optimize("-fsched-spec") #pragma GCC optimize("unroll-loops") #pragma GCC optimize("-falign-jumps") #pragma GCC optimize("-falign-loops") #pragma GCC optimize("-falign-labels") #pragma GCC optimize("-fdevirtualize") #pragma GCC optimize("-fcaller-saves") #pragma GCC optimize("-fcrossjumping") #pragma GCC optimize("-fthread-jumps") #pragma GCC optimize("-funroll-loops") #pragma GCC optimize("-fwhole-program") #pragma GCC optimize("-freorder-blocks") #pragma GCC optimize("-fschedule-insns") #pragma GCC optimize("inline-functions") #pragma GCC optimize("-ftree-tail-merge") #pragma GCC optimize("-fschedule-insns2") #pragma GCC optimize("-fstrict-aliasing") #pragma GCC optimize("-fstrict-overflow") #pragma GCC optimize("-falign-functions") #pragma GCC optimize("-fcse-skip-blocks") #pragma GCC optimize("-fcse-follow-jumps") #pragma GCC optimize("-fsched-interblock") #pragma GCC optimize("-fpartial-inlining") #pragma GCC optimize("no-stack-protector") #pragma GCC optimize("-freorder-functions") #pragma GCC optimize("-findirect-inlining") #pragma GCC optimize("-fhoist-adjacent-loads") #pragma GCC optimize("-frerun-cse-after-loop") #pragma GCC optimize("inline-small-functions") #pragma GCC optimize("-finline-small-functions") #pragma GCC optimize("-ftree-switch-conversion") #pragma GCC optimize("-foptimize-sibling-calls") #pragma GCC optimize("-fexpensive-optimizations") #pragma GCC optimize("-funsafe-loop-optimizations") #pragma GCC optimize("inline-functions-called-once") #pragma GCC optimize("-fdelete-null-pointer-checks")
这个的最大用处是:当你用上这些还TLE时,你程序已经没救了。

九.其他

其实这里才是精华

inline和register尽量用

注意inline不要在递归函数里用

register不要在递归过程中用,回溯时可以用

自加自减运算符前置,就是i++改成++i

内存允许且不需memset时bool改int

能int绝对不要用long long(一哥们数颜色(洛谷上兔子那个,不是带修莫队)调了一下午一直TLE,我把他long long改成int就A了……)

如果方便可以循环展开,偶尔有奇效

if-else能改就改成三目运算符?:

边权为1的图跑最短路用bfs别用dijkstra

(一个名叫Journeys的题的暴力bfs比dijstra高10分···)

多维数组顺序按for循环来!!!

eg. CodeForces 372CWatching Fireworks is fun

dp[N][M]与dp[M][N]一个TLE60,一个AC

其实就是内存访问的连续性,一直跳跃着访问内存自然慢

数组维数越少越好

离散化可以利用pair记录下标,映射时更加方便,不用再lower_bound了

还有一个玄学操作叫做卡时

就是你打了个dfs,里面用clock()判断运行时间

快TLE的时候直接输出当前答案

注意clock()返回的时间不是特别准

别把跳出时间定的太极端

还有注意Linux下1e6是一秒,想象一下跳出时间设成1000的酸爽

完结撒花~~

转载于:https://www.cnblogs.com/remarkable/p/11216246.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值