压行导论
ps: 持续更新唉,像我这么努力的 鸽子 作者已经不多了,跪求评论推荐关注QWQ (实在不行,浏览器里收藏也行啊)
ps2: 如果在更新中,可能出现部分不完整,不通顺的情况,请见谅(这是因为怕打到一半出现意外就先保存)
压行是个好东西,本文将以luoguP5661(本代码提交0分)、线段树模版和其余零碎代码为例,向你讲解如何正确压行
原版未压行(格式化邪教):
#include<bits/stdc++.h>
using namespace std;
int opt,n,ans,top,m=1,k;
int t[100010],p[100010],yh[100010],sj[100010];
bool r[100010];
int main() {
cin>>n;
for(int i=1; i<=n; i++) {
cin>>opt>>p[i]>>t[i];
if(opt==0) {
yh[++top]=p[i],sj[top]=t[i],ans+=p[i];
} else {
k=0;
for(int j=m; j<=top; j++) {
if(r[j]) continue;
if(t[i]+sj[j]>45) m=j;
else if(yh[j]>=p[i]) {
k=j;
r[k]=true;
break;
}
}
if(!k) ans+=p[i];
}
}
cout<<ans;
}
0.为什么压行
- 技术含量不高
- 使代码更短
- 可读性更高(划掉)
- 方便调试(划掉)
虽然格式化邪教也有着不少优点,但是它缺少了动手完成的成就感 ,而且,压行,是神教,正道!!!!!!!!!!!!!!!!!!!!!!!
有人说
华丽的压行技巧并不能提升代码水平,只有不到位的压行技巧导致爆零。
但是,压行一定可以保证代码的可读性
I.压行基础
I.I 逗号的使用
当多条赋值语句和函数各占一行时,会让人不爽,这时,逗号出现了,他可以对其进行连接(注意,他的优先级最低,且返回最后一个值)。
(创建变量也可以使用)
原版
k=j;
r[k]=true;
压行
k=j,r[k]=true;
I.II 常数替代
原版代码的定义中我们可以发现数据范围反复出现,此时我们可以定义常量。
原版:
int t[100010],p[100010],yh[100010],sj[100010];
bool r[100010];
压行?(应该只是压代码):
//不建议用const,constexpr可以自己识别某表达式是否是常量
//注:constexpr 为C++11关键字
constexpr int N=100010;
int t[N],p[N],yh[N],sj[N];
bool r[N];
I.III 删掉大括号
大括号有时候必须得用到,但是它的出现必定会使代码的行数增加三行至以上,当我们用其他压行技巧把大括号里的内容压到只有一行时,删掉他(函数不行)!
例
for (int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
scanf("%d",&a[i][j]);
}
}
压行for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",a[i][j]);
II. 中级压行
II.I 三目运算符
条件 ? 如果为真执行 : 如果为假执行 里面不能塞代码块哟
if else可以扔掉了,再注意一下优先级即可。
记住一句话
条件判断,三元运算
(一次偶然发现的,我真是太聪明了)
如max函数
int max(int a,int b)
{
if(a>b) return a;
else return b;
}
int max(int a,int b){return a>b?a:b;}
注意:三元运算符不能完全代替if else,比如for,while,goto,break,continue是不能塞进去的,但lambda表达式会解决此烦恼,后面会讲
II.II 短路原理
请先看代码:
#include<stdio.h>
int main()
{
int a=100,b=200;
if(a>99&&b++)
{
if(a>200&&b=b-2) ;
}
printf("%d %d",a,b);
return 0;
}
输出为100 201
这就是短路性质:
如果逻辑判断中存在多个条件,按照优先级,只要能凭借第一个(前几个)判断式子为真,便不对逻辑判断条件中的后面部分进行操作
以两个条件为例(因为无论多少个(显然大于等于 \(2\) ),均可认为多个两个条件组成,):
- 运算符为与(
&&)时,A&&B中,若A为false,便不对B进行判断;只有A为true,才会对B进行判断 - 而运算符为或(
||)是,A||B中,若A为true,便不对B进行判断;只有A为false,才会对B进行判断
而例如a=b+1返回的值是计算后的a,所以通过这种方法我们可以把:
if(a%b==0)
{
sum++;
break;
}
改为
if(a%b==0&&++sum) break;//不要sum++防止sum的初始为0而导致条件为假
注:跟科学中的电路同理,都容易使电路(代码)坏掉或无法实现所希望的功能
II.III 熟练的STL
C++中的STL库真的很好用的(不要跟我一样用C++但沉迷于C语言无法自拔)
比如map可以自定义下标 就map<int,int>好像没用对吧,这个下标可以存负数的,内部利用红黑树自动排序,但是取用元素时好像是O(log n)
由于我对STL没有过多了解,就不误人子弟了
III 高级压行
III.I 巧用define
define里可以塞一些“变量”,例如大家常用的#define max(a, b) ((a)>(b)? (a): (b)),#define lowbit(x) ((x)&-(x))
##是define里的连接符:它可以将两个常量字符串或变量字符串连接。
于是我们可以写一个这样的define句子:#define rep(i, a, b) for(int i=(a), i##up=(b); i<=i##up; ++i)
引用自https://www.luogu.com/article/rb9991an
但是,需要注意的是define只是单纯的替换
如ans=max(fun1(),ans),他完全等价于ans=(fun1()>ans?fun1(),ans),这意味着fun1函数将会执行两次,而不是先计算fun1()的值,在进行比较,将ans置为其中计算好的较大的值。
所以,define虽好,但也不可以滥用,以免造成时间复杂度上升。
以下是一些我常用的宏定义:
#define _rep(i,st,n) for(int i=(st);i<(n);++i)
#define rep(i,st,n) for(int i=(st);i<=(n);++i)
#define _dwh(i,st,n) for(int i=(st);i>(n);--i)
#define dwh(i,st,n) for(int i=(st);i>=(n);--i)
III.II 巧用lambda表达式
上文中我们提到三元运算符中不能放代码块,但是lambda表达式可以。
但他仍类似于函数(也要定义),且经格式化邪教后大括号会展开,所以更多应用于sort中,
例:
sort(a+1,a+1+n,[](const node& x,const node& y){return /* TODO */;});
关于它的使用请自行bdfs或这边粗略看一下,没时间讲太多。
捕获变量时建议隐式引用捕获,这样你可以不用写太多。
例子:
for(scanf("%d",&n);i<=n;i++)
{
scanf("%d%d%d",&opt,p+i,t+i);
if(!opt) yh[++top]=p[i],sj[top]=t[i],ans+=p[i]
else
{
func();
for(k=0,f=1,j=m;j<=top&&f;j++) r[j]?1:(t[i]-sj[j]>45?m=j:(yh[j]>=p[i]&&(k=j)&&(r[k]=true)?f=0:1));
if(!k) ans+=p[i];
}
}
运用lambda技巧后
auto func=[&](){for(k=0,f=1,j=m;j<=top&&f;j++) r[j]?1:(t[i]-sj[j]>45?m=j:(yh[j]>=p[i]&&(k=j)&&(r[k]=true)?f=0:1));};
for(scanf("%d",&n);i<=n;i++) scanf("%d%d%d",&opt,p+i,t+i),(!opt)?yh[++top]=p[i],sj[top]=t[i],ans+=p[i]:(func(),!k?ans+=p[i]:6);
当lambda表达式塞在三元运算符中,由于三元运算符强制要求有返回值,且lambda默认是void,所以,你应:
XXXX?XXX:(func(),0);
或在定义时就明确指出返回值不是void
auto func=[]()->int{114514;};
//...
XXXX?XXX:func();
你在使用掉lambda表达式,格式化行数仍不变,且代码可读性真的会很好,所以慎用
IV. 其他技巧
(零零散散的,以后再整理吧)
IV.I 压输入输出
输入时可以压行到变量的定义中(我有时候压到for循环中)的,使用逗号运算符即可(cin cout输入本质上是一个类的重载运算符)
例:
int x,y=(cin>>x,0);
由于scanf,printf函数的参数读入是依赖第一个字符串所指代的内存。所以scanf("%d",&x,y=100);是可以的
但是你得保证后续没有任何数据读入
因而方法有时会造成爆0,不建议使用
IV.II 使用工具
必备工具:压行机
注:有时候可能太卡进不去,所以建议Ctrl + S先行保存
当真的压无可压的时候可使用(所以,这往往才是压行的最后一步)
IV.III 剩余的其他
- 主函数中int可省略,这是默认的。但有时CE,慎
return 0;也可以省略。但有时会WA掉,慎用printf返回的是输出长度(显然不会为0)所以是return!printf("%d",ans);for可等效代替while,简短语句(把变量定义放进去时,一定要注意作用域的问题,循坏外就不能用这些变量了)#include可以改成import
V. 易错情况
VI.I 短路原理之CE
对应赋值语句,进行短路原理压行时应注意不能直接&&,需加括号。
if(x%i==0&& b[++cnt]=x) break;
这样是不行的
if(x%i==0&&(b[++cnt]=0)) break;
这样是被允许的,但还存在潜在问题,我们马上就谈
V.II 短路原理之赋值为0
先瞧以下代码
//...
if(x%i==0&&(b[++cnt]=x)) break;
//...
不难理解出这段用短路原理压行代码是当x%i==0时,将b[++cnt]=x并跳出循环
可当x=0时,这会被判定为false,你就喜提WA了
所以运用短路原理时,当不能确定被短路部分的值一定非 \(0\) 时,请将与b[++cnt]=x类型的改成(b[++cnt]=x,1)以保证被短路部分的值一定非 \(0\) 判 true
(作者对线段树压行后血的教训,找了一个小时)
VI 线段树模板实例
学了以上技巧,让我们进行以下练习吧(当然不用```lambda``进行极限压行,不然怕太丧心病狂)。
先亮出初始代码(正常打就是半压行的哦)
//如果打错请告知,还有部分基础的已经压了
#include<stdio.h>
#define rep(i,st,n) for(int i=(st);i<=(n);++i)
const int
MAXN=4e5,
MAX_ADD=10,
N=MAXN+MAX_ADD;
int n,m,
a[N],tag[N],sum[N],d[N];
void build(int v,int l,int r)
{
if(l==r)
{
sum[v]=a[l];
return;
}
int mid=l+r>>1;
build(v<<1,l,mid);
build(v<<1|1,mid+1,r);
sum[v]=sum[v<<1]+sum[v<<1|1];
}
void maketag(int v,int l,int r,int k){sum[v]+=(r-l+1)*k,tag[v]+=k;}
void downtag(int v,int l,int r)
{
if(!tag[v]) return;
int mid=l+r>>1;
maketag(v<<1,l,mid,tag[v]),maketag(v<<1|1,mid+1,r,tag[v]);
tag[v]=0;
}
void update(int v,int l,int r,int x,int y,int k)
{
if(x<=l&&y>=r)
{
maketag(v,l,r,k);
return;
}
if(tag[v]) downtag(v,l,r);
int mid=l+r>>1;
if(x<=mid) update(v<<1,l,mid,x,y,k);
if(y>mid) update(v<<1|1,mid+1,r,x,y,k);
sum[v]=sum[v<<1]+sum[v<<1|1];
}
int query(int v,int l,int r,int x,int y)
{
if(x>r||y<l) return 0;
if(x<=l&&y>=r) return sum[v];
int mid=l+r>>1;
if(tag[v]) downtag(v,l,r);
return query(v<<1,l,mid,x,y)+query(v<<1|1,mid+1,r,x,y);
}
int main()
{
return 0;
}
首先define和部分压行基础内容已经完成了,很好理解,现在,正式开始
VI.I build函数
void build(int v,int l,int r)
{
if(l==r)
{
sum[v]=a[l];
return;
}
int mid=l+r>>1;
build(v<<1,l,mid);
build(v<<1|1,mid+1,r);
sum[v]=sum[v<<1]+sum[v<<1|1];
}
不难发现,if可压,还有后面函数调用和sum计算可用逗号。
值得注意的是,运用短路原理时,你不能直接&& sum[v]=a[l],会CE,你要加个括号。
同时,为防止a[l]值为 \(0\) 的毒瘤,应该写为 (sum[v]=a[l],1)。《论在这因毒瘤数据找了一个小时的痛苦》
最后,这函数就大功告成了
void build(int v,int l,int r)
{
if(l==r&&(sum[v]=a[l],1)) return;
int mid=l+r>>1;build(v<<1,l,mid),build(v<<1|1,mid+1,r),sum[v]=sum[v<<1]+sum[v<<1|1];
}
VI.II maketag函数
就只有压行基础,没什么好讲的,已压,过
VI.III downtag函数
同上,只放压过的代码
void downtag(int v,int l,int r){if(!tag[v]) return;int mid=l+r>>1;maketag(v<<1,l,mid,tag[v]),maketag(v<<1|1,mid+1,r,tag[v]),tag[v]=0;}
VI.III update函数
注意哦,maketag函数没有返回值哦,要有括号和逗号使其为 \(1\) 即可,还有这么多条件判断,虽然没有else,但并不影响使用三元运算符。
代码呈上。
void update(int v,int l,int r,int x,int y,int k)
{
if(x<=l&&y>=r&&(maketag(v,l,r,k),1)) return;
if(tag[v]) downtag(v,l,r);
int mid=l+r>>1;
x<=mid?update(v<<1,l,mid,x,y,k):0,y>mid?update(v<<1|1,mid+1,r,x,y,k):0,sum[v]=sum[v<<1]+sum[v<<1|1];
}
VI.IV query函数
同上,条件判断,三元运算。
附代码
int query(int v,int l,int r,int x,int y)
{
if(x>r||y<l) return 0;
if(x<=l&&y>=r) return sum[v];
int mid=l+r>>1;
return tag[v]?(downtag(v,l,r,1),0):1,query(v<<1,l,mid,x,y)+query(v<<1|1,mid+1,r,x,y);
}
就这样,压行完成了\(^o^)/~
附上最终结果。
//压行机未启用,如果CE或其他错误,可告知
#include<stdio.h>
#define rep(i,st,n) for(int i=(st);i<=(n);++i)
const int
MAXN=4e5,
MAX_ADD=10,
N=MAXN+MAX_ADD;
int n,m,
a[N],tag[N],sum[N],d[N];
void build(int v,int l,int r)
{
if(l==r&&(sum[v]=a[l],1)) return;
int mid=l+r>>1;build(v<<1,l,mid),build(v<<1|1,mid+1,r),sum[v]=sum[v<<1]+sum[v<<1|1];
}
void maketag(int v,int l,int r,int k){sum[v]+=(r-l+1)*k,tag[v]+=k;}
void downtag(int v,int l,int r){if(!tag[v]) return;int mid=l+r>>1;maketag(v<<1,l,mid,tag[v]),maketag(v<<1|1,mid+1,r,tag[v]),tag[v]=0;}
void update(int v,int l,int r,int x,int y,int k)
{
if(x<=l&&y>=r&&(maketag(v,l,r,k),1)) return;
if(tag[v]) downtag(v,l,r);
int mid=l+r>>1;
x<=mid?update(v<<1,l,mid,x,y,k):0,y>mid?update(v<<1|1,mid+1,r,x,y,k):0,sum[v]=sum[v<<1]+sum[v<<1|1];
}
int query(int v,int l,int r,int x,int y)
{
if(x>r||y<l) return 0;
if(x<=l&&y>=r) return sum[v];
int mid=l+r>>1;
return tag[v]?(downtag(v,l,r,1),0):1,query(v<<1,l,mid,x,y)+query(v<<1|1,mid+1,r,x,y);
}
int main()
{
return 0;
}
结语
最后,勤加练习,你的压行便会大成
另,差点忘附上用上几乎所有技巧的压行版P5661
//本代码提交0分(但改的不多,你或许可以找找看是哪里错的)
#include<stdio.h>
const int N=100010;
int opt,n,ans,top,m=1,k,f,t[N],p[N],yh[N],sj[N],r[N];
int main() {
int i=1,j;
for(auto func=(scanf("%d",&n),[&](){for(k=0,f=1,j=m;j<=top&&f;j++) r[j]?1:(t[i]-sj[j]>45?m=j:(yh[j]>=p[i]&&(k=j)&&(r[k]=true)?f=0:1));});i<=n;i++) scanf("%d%d%d",&opt,p+i,t+i,(!opt)?yh[++top]=p[i],sj[top]=t[i],ans+=p[i]:(func(),!k?ans+=p[i]:6));
return!printf("%d",ans);
}//为了代码的可读性,还未使用压行机
如果以正常人的角度来看这篇文章:
- 压行基础: 优良的码风
- 中级压行和define: 还行,就是有些地方改改就好了
- 死里压缩的lambda表达式: 简直是丧心病狂(你已经见识到了)
- 至于压行机: 你不格式化人家都不看一眼的
(如需转载,请标明出处)
本人实力不济,如有错误或其他建议及补充(包括但不限于压行技巧),欢迎指出。
更新日志(残)
2024.10.2 发布
2025.1.17 把零碎知识搬了过来
2025.4.26 添更新日志,增加一丁点内容,突然发现:
不允许修改阅读量超过 100 的随笔的发布时间
2025.6.29 增添易错情况一栏(ps:期末考完了,突然想起自己有个博客(ps2:但等到作业布置后应该就不会更新,这几天可能比之前勤点)),修改之前对短路性质的说明(之前好像不对,误人子弟了啊,抓狂.jpg ),增添正常人角度看此文(笑)
2025.6.30 修改一丁点内容,增加线段树实例,增添易错情况之CE
2025.7.5 修改部分代码错误
参考资料
压行万岁

浙公网安备 33010602011771号