NOIP卡常数技巧

NOIP卡常数技巧
https://blog.csdn.net/a1351937368/article/details/78162078
http://www.mamicode.com/info-detail-2379526.html?tdsourcetag=s_pcqq_aiomsg
将上述两文内容进行合并如下:
1.IO优化
fread 和 fwrite ,如果还想再优化有mmap….(然而并不会用,好像也没用。。。)
读入优化(这个非常重要!!!!!!!)
inline int Read()
{
int x=0,f=1;char c=getchar();
while(c>‘9’||c<‘0’) {if(c==’-’) f=-1;c=getchar();}
while(c>=‘0’&&c<=‘9’) {x=x10+c-‘0’; c=getchar();}
return x
f;
}
当然还有另一种但是这种只适用于读取非负数如下:
#inline int read(){
int num;
char ch;
while((ch=getchar())<‘0’ || ch>‘9’);
num=ch-‘0’;
while((ch=getchar())>=‘0’ && ch<=‘9’){
num=num*10+ch-‘0’;
}
return 0;
}
输出优化:
inline void out(int x){
if(x>=10){
out(x/10);
}
putchar(x%10+‘0’);
}
大家注意一下快速读入和快速输出尽量爆int的数据就尽量不要用了我原来用快速输出就WA掉了但是用printf就没有问题

2 inline
在声明函数之前写上inline修饰符(就像上面Read()一样),可以加快一下函数调用,但只能用于一些操作简单的函数。涉及递归,大号的循环等很复杂的函数,编译器会自动忽略inline。

3 register
在定义变量前写上register修饰符,用于把变量放到CPU寄存器中,适用于一些使用频繁的变量:register int n,m;
寄存器空间有限,如果放得变量太多,多余变量就会被放到一般内存中;
快,不是一般的快,快到什么程度呢?:
register int a=0;
for(register int i=1;i<=999999999;i++)
a++;
int a=0;
for(int i=1;i<=999999999;i++)
a++;
结果:
优化:0.2826 second
不优化:1.944 second
恐怖啊!!!!

4 循环展开
循环展开也许只是表面,在缓存和寄存器允许的情况下一条语句内大量的展开运算会刺激 CPU 并发(前提是你的 CPU 不是某 CPU)…
用法(下面是一个将一个(int) 类型数组初始化为(0)的代码段):

void Init_Array(int *dest, int n)
{
int i;
for(i = 0; i < n; i++)
dest[i] = 0;
}
而如果用循环展开的话,代码如下:
void Init_Array(int *dest, int n)
{
int i;
int limit = n - 3;
for(i = 0; i < limit; i+= 4)//每次迭代处理4个元素
{
dest[i] = 0;
dest[i + 1] = 0;
dest[i + 2] = 0;
dest[i + 3] = 0;
}
for(; i < n; i++)//将剩余未处理的元素再依次初始化
dest[i] = 0;
}
循环引发的讨论
请看下面的两段代码,
代码1:
for(int i = 0; i < n; ++i)
{
fun1();
fun2();
}
代码2:
for(int i = 0; i < n; ++i)
{
fun1();
}
for(int i = 0; i < n; ++i)
{
fun2();
}
注:这里的(fun1())和(fun2())是没有关联的,即两段代码所产生的结果是一样的。

以代码的层面上来看,似乎是代码(1)的效率更高,因为毕竟代码(1)少了(n)次的自加运算和判断,毕竟自加运算和判断也是需要时间的。但是现实真的是这样吗?

这就要看(fun1)和(fun2)这两个函数的规模(或复杂性)了,如果这多个函数的代码语句很少,则代码(1)的运行效率高一些,但是若(fun1)和(fun2)的语句有很多,规模较大,则代码(2)的运行效率会比代码(1)显著高得多。可能你不明白这是为什么,要说是为什么这要由计算机的硬件说起。

由于(CPU)只能从内存在读取数据,而(CPU)的运算速度远远大于内存,所以为了提高程序的运行速度有效地利用(CPU)的能力,在内存与(CPU)之间有一个叫(Cache)的存储器,它的速度接近(CPU)。而(Cache)中的数据是从内存中加载而来的,这个过程需要访问内存,速度较慢。

这里先说说(Cache)的设计原理,就是时间局部性和空间局部性。时间局部性是指如果一个存储单元被访问,则可能该单元会很快被再次访问,这是因为程序存在着循环。空间局部性是指如果一个储存单元被访问,则该单元邻近的单元也可能很快被访问,这是因为程序中大部分指令是顺序存储、顺序执行的,数据也一般也是以向量、数组、树、表等形式簇聚在一起的。

看到这里你可能已经明白其中的原因了。没错,就是这样!如果(fun1)和(fun2)的代码量很大,例如都大于(Cache)的容量,则在代码(1)中,就不能充分利用(Cache)了(由时间局部性和空间局部性可知),因为每循环一次,都要把(Cache)中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了(Cache),利用两个循环语句,每个循环所用到的数据几乎都已加载到(Cache)中,每次循环都可从(Cache)中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出(fun1)的数据(1)次即可。
5 取模优化(仅O2)
//设模数为 mod
inline int inc(int x,int v,int mod){x+=v;return x>=mod?x-mod:x;}//代替取模+
inline int dec(int x,int v,int mod){x-=v;return x<0?x+mod:x;}//代替取模-
6 前置 ++
后置 ++ 需要保存临时变量以返回之前的值,在 STL 中非常慢。事实上,int 的后置 ++ 在实测中也比前置 ++ 慢 0.5 倍左右(UOJ 上自定义测试)
7 不要开bool,
所有bool改成char,int是最快的(原因不明)。
8 if()else语句比()?()?)语句要慢,
逗号运算符比分号运算符要快。
9 数据结构用指针代替数组(个人觉得无关紧要)
数组在用方括号时做了一次加法才能取地址!
所以在那些计算量超大的数据结构中,你每次都多做了一次加法!!!在 64 位系统下是   long long 相加,效率可想而知。
10除法运算优化
无论是整数还是浮点数运算,除法都是一件运算速度很慢的指令,在计算机中实现除法是比较复杂的。所以要减少除法运算的次数,下面介绍一些简单方法来提高效率:
通过数学的方法,把除法变为乘法运算,如if((a > b/c)),如果(a)、(b)、(c)都是正数,则可写成(if(a*c > b))
让编译器有优化的余地,如里你要做的运算是(in)t型的(n/8)的话,写成((unsigned)n/8)有利于编译器的优化。而要让编译器有优化的余地,则除数必须为常数,而这也可以用(const)修饰一个变量来达到目的。

11位运算优化
内容比较多,但是对程序的优化很大,建议大家学一下。
• 乘以(2)运算
int mulTwo(int n){//计算n2
return n << 1;
}
• 除以(2)运算
int divTwo(int n){//负奇数的运算不可用
return n >> 1;//除以2
}
• 乘以(2)的(m)次方
int mulTwoPower(int n,int m){//计算n
(2^m)
return n << m;
}
• 除以(2)的(m)次方
int divTwoPower(int n,int m){//计算n/(2^m)
return n >> m;
}
• 判断一个数的奇偶性
boolean isOddNumber(int n){
return (n & 1) == 1;
}
• 取绝对值(某些机器上,效率比(n>0) (?) (n:-n) 高)
int abs(int n){
return (n ^ (n >> 31)) - (n >> 31);
/* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1
若n为正数 n0=0,数不变,若n为负数有n-1 需要计算n和-1的补码,然后进行异或运算,
结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */
}
• 取两个数的最大值(某些机器上,效率比(a>b) (?) (a:b)高)
int max(int a,int b){
return b & ((a-b) >> 31) | a & (~(a-b) >> 31);
/如果a>=b,(a-b)>>31为0,否则为-1/
}
• 取两个数的最小值(某些机器上,效率比(a>b) (?) (b:a)高)
int min(int a,int b){
return a & ((a-b) >> 31) | b & (~(a-b) >> 31);
/如果a>=b,(a-b)>>31为0,否则为-1/
}
• 判断符号是否相同
boolean isSameSign(int x, int y){ //有0的情况例外
return (x ^ y) >= 0; // true 表示 x和y有相同的符号, false表示x,y有相反的符号。
}
• 计算(2)的(n)次方
int getFactorialofTwo(int n){//n > 0
return 2 << (n-1);//2的n次方
}
• 判断一个数是不是(2)的幂
boolean isFactorialofTwo(int n){
return n > 0 ? (n & (n - 1)) == 0 : false;
/如果是2的幂,n一定是100… n-1就是1111…
所以做与运算结果为0
/
}
• 对(2)的(n)次方取余
int quyu(int m,int n){//n为2的次方
return m & (n - 1);
/如果是2的幂,n一定是100… n-1就是1111…
所以做与运算结果保留m在n范围的非0的位
/
}
• 求两个整数的平均值
int getAverage(int x, int y){
return (x + y) >> 1;


定义常量优化
• 在进行较大数字时,可以#define一下例如某道题中需要多次(mod):
(mod=10^9+7)
如果您将(mod)作为一个(long) (long) 或者是(int)变量来打,那么恭喜您(TLE)((3518ms),时限(3s))
而如果把(1000000007)换成了#const int (mod) $1000000007 $
那么您的程序就是(AC)了((2398ms),时限(3s))
那么#define在定义数组大小的时候是首选
(const)在运算(比如(mod))是首选


数组优化

o (C++)里面(STL)自带的(vector)存取效率不高,在可能的情况下用指针数组代替会大幅提高性能。
• 关于(memset)和(memcpy)以及(memmove)
这几个函数效率都非常高,比循环的速度快很多
其实用法也很简单,比如原来是这样的
`for(int i=l;i<=r;++i) a[i]=0; for(int i=l;i<=r;++i) a[i]=b[i]; for(int i=l;l<=r;++i) a[i]=b[i],b[i]=0;
我们可以优化成
memset(a+l,0,r-l+1<<2);
memcpy(a+l,b+l,r-l+1<<2);
memmove(a+l,b+l,r-l+1<<2);
注意,左移的位数和(a),(b)的类型有关,这里默认为(int),(sizeof(int)=4),所以就是左移(2)(乘(4))
如果不知道(a),(b)类型所占字节数,可以改成如下
memset(a+l,0,(r-l+1)*sizeof(a[0]));
memcpy(a+l,b+l,(r-l+1)*sizeof(a[0]));
memmove(a+l,b+l,(r-l+1)*sizeof(a[0]));

注意:
• (register)变量不能开太多,它本身也优化不了多少,再出点什么事情可就真(GG)了,暂时用不到的变量不要过早的初始化,它会存在你的缓存里,如果之后继续调用该变量,速度会较快但若在之后调用许多其他变量,则会将该变量清出你的缓存,之后再有对该变量的操作时,则会花费比从缓存中调用较长的时间调用该变量
• 在遍历高维数组时,并在定义数组时将元素多的维度放在靠前的位置,将循环次数多的维度放在外层,可减少一定的运行时间。高维数组在内存中都是线性安放的,在C语言中,按照的是行优先顺序,就像上面提到的。当我们使用行优先顺序遍历数组时,恰好就是顺次访问内存数据,会非常有利于(CPU)高速缓存的工作
• 循环变量开为形如(register) (int) (i)的形式,看上去是一个优化,但事实上,编译器并不傻,它会在汇编中给你搞成(register)。同样的,循环变量在自增/自减时,写为形如(++i/–i)的形式,它看上去也是对(i++)的一种优化,但在汇编中,它也相应的优化成的类似(++i)的操作.
例题:
• CF86D
莫队,不卡常数非常难过。
• BZOJ3815
正如题,一些程序进行常数优化后可以过
• BZOJ3583
正解需要卡常数。

终于讲完了,如果你还想更更深入的学,推荐一本书:
《论程序底层优化的一些方法与技巧》

发现新大路(没看过):https://www.luogu.org/blog/user3296/oi-zhong-jian-dan-di-chang-shuo-you-hua

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值