简化,再简化(编程技巧与知识讲解day2)

这期单从技术不好讲,来些例题吧。
本帖含有三个例题,均为入门题,第二,三题有扩展


【例1】计算e的值

题面
很多人萌新看到这一题都会不假思索地写一个双重循环。
如下:

for(int i=0;i<=n;i++){
	int f=1;
	for(int j=1;j<=i;j++){
		f*=j;
	}
	e+=1.0/f;
}

但这是错的
为什么呢?运行结果没错,但不够快
因为每次不用都计算一遍阶乘。
可以发现:(n+1)!=n!*(n+1)
也就是说,每一轮的阶乘值可以由上一轮更新而来
代码很容易就出来了。
核心代码:

for(int i=1;i<=n;i++){
	e+=1.0/f;
	f*=i;
}

注意:f要赋初值为1。
降低循环次数,减少循环层数是优化代码的常用方法。


【例2】百鸡问题

题面
很多萌新看到这题都会直接敲一个三重循环,但三重循环的效率太低了,于是可以考虑减少循环次数

简化

可以发现,当知道公鸡和母鸡的数量时,也就可以算出小鸡的数量
核心代码:

for(long long i=0;i<=m;i++){
	for(long long j=0;j<=m-i;j++){
		if((m-i-j)%z==0&&i*x+j*y+(m-i-j)/z==n) cnt++;
	}
}

再简化

有没有效率更高的方法?
有的,那就是数学
(我 LaTeX 中的大括号不会打,请大佬见谅)
解:设公鸡数量为 x ,母鸡数量为 y ,小鸡数量为 z
ax+by+\dfrac{z}{c}=n 1.
x+y+z=m 2.
由2.得: z=m-x-y 3.
将3.带入1.得: ax+by+\dfrac{m-x-y}{c}=n
由1.去分母得: c(ax+by)+m-x-y=cn
由1.化简得: y=\dfrac{cn-m-(ac-1)x}{bc-1}
很明显,这是一个二元一次不定方程
枚举 x ,判断 y 是否符合条件
判断方法:
判断 bc-1 是否能被 cn-m-(ac-1)x 整除。
注: a,b,c 在程序中的输入为 x,y,z ,为符合数学习惯,将系数命名为 a,b,c
代码:if((z*n-m-(x*z-1))%(y*z-1)==0) cnt++;


bc-1=0 则程序会发生 \color{red}{Runtime\,\,error}需要特判
特别的,若 bc-1=0 ,则,
b=1,c=1
ax+y+z=n 1.
x+y+z=m 2.
解:将1.减去2.得 (a-1)x=n-m
∴x=\dfrac{n-m}{a-1} (若除不尽直接输出 0
再用一重循环枚举 y,z


你若告诉我 a-1=0 的话……
先列出方程。
x+y+z=n
x+y+z=m
n≠m 直接输出 0
否则,

直接上排列组合


排列组合——隔板法

n 个数分成 m 份共有多少种方案(每份个数为正整数)?
可以把这 n 个数排成一排:
1\,\,2\,\,3\,\,4\,\,5
插入 m-1 个板子将它们分成 m 份,
共有 n-1插入板子。
根据组合数,有 C_{m-1}^{n-1} 种选法。


很明显,这个问题与就是隔板法m=3 的时候的特殊情况
x,y,z 都有可能为 0
所以还要把这种情况加上去:
当其中之二为 0 时:
cnt=3 (分别为 x=n,y=n,z=n 三种情况。)
当其中之一为 0 时:
cnt=3C_1^{n-1}=3(n-1)
当所有数都不为 0 时:
cnt=C_2^{n-1}=\dfrac{(n-1)(n-2)}{2}
所有情况:
cnt=3+3(n-1)+\dfrac{(n-1)(n-2)}{2}=\dfrac{n^2+3n+2}{2}


最后的完整代码不想写了
完整代码估计有三十几行(这么多行的代码当然算不上什么,但这是入门题啊)。


【例3】原材料加工

题面
这道题萌新一般用两重循环,而大佬直接套背包模版。
我来说一道萌新能理解,且不会太慢的方法。


优化

这个问题可以用动态规划解决(背包也是动态规划的一种)。
dp边界条件状态转移方程两部分组成。
动态规划关键在于状态转移方程
看看 dp_i 能等于什么?
可以等于 dp_{i-1}+1
也可以等于 dp_{i-a}
还能等于 dp_{i-b}
而我们要选择哪个呢?
题目中让我们求最小的,所以我们选取的当然要是最小的
代码:

for(int i=1;i<=n;i++){
	dp[i]=min(min(dp[i-1]+1,dp[i-a]),dp[i-b]);
}

其实这道题用动态规划在某些时候还是不如两重循环的,当 na,b 差距不大的时候。
其实这道题用不上 dp ,可以直接优化成一重循环。

for(int i=0;i<=n;i+=a){
	ans=min((n-i)%b,ans);
}

效率还远高于 dp .
所以我讲 dp 是干嘛?
背包
因为这道题太像背包了。
https://www.luogu.com.cn/problem/P1048
背包模版题。
核心代码:

for(int i=1;i<=m;i++){
	for(int j=1;j<=t;j++){
		if(a[i]<=j) dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i]);
		else dp[i][j]=dp[i-1][j];
	}
}

分析:
有两种转移方式

  1. dp_{i,j}=dp_{i-1,j} 直接转移
  2. dp_{i,j}=dp_{i-1,j-a_i}+b_i 在前 a_i 秒采摘第 i 株草药,价值增加 b_i

而我们要取最大值,所以状态转移方程是:
dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-a_i}+b_i)
敲到代码里就行了。


写完了,终于写完了。

9 个赞

赞呐

4 个赞

敲了1个小时1个赞都没有?

3 个赞

支持一下

3 个赞

谢谢大佬 :sob:

2 个赞

@张乐凡 现在有两个了

3 个赞

数学大佬%%%

3 个赞

我猜很多萌新先兴高采烈地点进来,然后越看越觉得不对劲,当看到“ 那就是数学”就崩了

4 个赞

我只是小学蒟蒻。

2 个赞

小学大佬%%%

3 个赞

还能与这道题结合一下,优化到 O(logn)

2 个赞

上热门了!!!
感谢大家支持!!!

1 个赞

1 个赞

想当年,我几乎每天都能上榜一两个,不过好像这里面也有我的

xyd完成了热榜没有一个常规的壮举

2 个赞

是的,但现在有了,我的上榜一了。

1 个赞

信友队完成了热榜四天从没有常规到全是常规的壮举。

1 个赞

1 个赞