压行导论

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中,若Afalse,便不对B进行判断;只有Atrue,才会对B进行判断
  • 而运算符为或(||)是,A||B中,若Atrue,便不对B进行判断;只有Afalse,才会对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 修改部分代码错误

参考资料

压行万岁

posted @ 2025-01-17 14:51  AC-13-13  阅读(458)  评论(5)    收藏  举报