夜深敲代码——记录一个优化过程
Posted 荔枝当大佬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了夜深敲代码——记录一个优化过程相关的知识,希望对你有一定的参考价值。
前言
最近一段时间OpenAI的热度一直不减,ChatGpt带来的极大便利性大家也是深有体会。荔枝也跟风搞了一个来玩玩,感觉还是很不错的哈哈哈哈。这不,最近开始刷题用ChatGpt帮忙找bug就很舒服,它甚至可以为我们提供优化的思路,帮助我们找到自己思维上的漏洞,对于像荔枝这样的算法小白的刷题进阶过程提供了很大的帮助哈哈哈哈哈。
文章目录
PTA乙级1007——素数对猜想
我们首先来看看题目,其实这道题目思路还是很简单,就是荔枝对于STL库的掌握程度还是不够,写起来不是很得心应手。
题目描述
让我们定义dn为:dn=Pn+1一Pn,其中P是第i个素数。显然有d=1,且对于>1有d是偶数。“素数对猜想”认为“存在无穷多对相邻且差为2的素数”。现给定任意正整数N(N<10^5),请计算不超过N的满足猜想的素数对的个数。
输入格式:
输入在一行给出正整数
N
。输出格式:
在一行中输出不超过
N
的满足猜想的素数对的个数。输入样例:
20
输出样例:
4
其实整个过程最主要的就是素数如何判定,荔枝一开始感觉很简单,直接头铁暴力写(这里其实荔枝也看了数据的范围,感觉会超时哈哈哈)
#include <bits/stdc++.h>
using namespace std;
//定义一个函数来查找所有的素数
vector<int> v1;
void allnum(int n)
for(int i=2;i<n;i++)
bool is_prime = true;
for(int j=2;j<i;j++)
if(i%j==0) is_prime=false;
if(is_prime)
v1.push_back(i);
int main()
int num = 0;
int n;
cin>>n;
allnum(n);
for(int i=0;i<v1.size();i++)
if(find(v1.begin(),v1.end(),v1[i]+2) != v1.end()) num+=1;
cout<<num<<endl;
return 0;
看了一下测试用例,跑过了
提交后发现果然超时了,并且逻辑有点问题
这时候除了素数判定那部分用一些方法来减少遍历之外,荔枝空荡荡的脑门中感觉没啥思路优化,于是我就直接把代码丢给GPT纠错了哈哈哈
是的,可以看到ChatGpt给出了它的改进建议以及模范demo:
#include <bits/stdc++.h>
using namespace std;
// 定义一个函数来查找所有的素数
void allnum(int n, unordered_set<int>& s)
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i <= n; i++)
if (is_prime[i])
s.insert(i);
for (int j = i * 2; j <= n; j += i)
is_prime[j] = false;
int main()
int n;
cin >> n;
unordered_set<int> primes;
allnum(n, primes);
int num = 0;
for (auto x : primes)
if (primes.count(x + 2))
num++;
cout << num << endl;
return 0;
果然,还是GPT牛呀,直接就满分答卷了呜呜呜。这里它用的是关联哈希容器unordered_set,好吧之前没怎么用过,不是很熟悉,顺带查了一下它的基本用法和哈希表的特点哈哈哈哈。
PTA乙级1005——继续3n+1猜想
顺带用unordered_set写了写之前的这道题哈哈哈,感觉还不错。
题目描述
卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。
当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对 n=3 进行验证的时候,我们需要计算 3、5、8、4、2、1,则当我们对 n=5、8、4、2 进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这 4 个数已经在验证3的时候遇到过了,我们称 5、8、4、2 是被 3“覆盖”的数。我们称一个数列中的某个数 n 为“关键数”,如果 n 不能被数列中的其他数字所覆盖。现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。
输入格式:
每个测试输入包含 1 个测试用例,第 1 行给出一个正整数 K (<100),第 2 行给出 K 个互不相同的待验证的正整数 n (1<n≤100)的值,数字间用空格隔开。
输出格式:
每个测试用例的输出占一行,按从大到小的顺序输出关键数字。数字间用 1 个空格隔开,但一行中最后一个数字后没有空格。
输入样例:
6
3 5 6 7 8 11
输出样例:
7 6
#include <bits/stdc++.h>
using namespace std;
int main()
unordered_set<int> s;
int n, x;
cin >> n;
while (n--)
cin >> x;
s.insert(x);
//C11的语法
for (auto x : s)
while (x != 1)
if (x % 2 == 0) x /= 2;
else x = (3 * x + 1) / 2;
if (s.count(x))
s.erase(x);
//使用这种迭代器传递的方式将整个迭代器复制过去
vector<int> ans(s.begin(), s.end());
sort(ans.rbegin(), ans.rend());
for (int i = 0; i < ans.size(); i++)
cout << ans[i];
if (i != ans.size() - 1) cout << " ";
return 0;
总结
在这篇文章中,荔枝主要记录了自己一个可能略显弱智的问题吧哈哈哈,荔枝是小白还请大家多多谅解哈。然后荔枝用ChatGpt来辅助自己改进代码,不得不说用ChatGpt来找东西真的很方便,比直接google快很多呢。
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~
夜深人静写算法(二十九)- 数位DP
文章目录
一、前言
数位 DP 又称 数位动态规划,在 LeetCode 上属于难题,而 ACM 竞赛中属于中等题,甚至可以说是模板题。
数位 DP 的状态设计千变万化,但万变不离其宗,只要确定这个题是用数位 DP 求解,基本就很容易把状态套出来。当然,对于刚接触动态规划的同学,建议先看下 背包问题、最长单调子序列、最长公共子序列、记忆化搜索 等基础内容,对状态机和状态转移有一个初步的认识,那么,在学习数位 DP 时能够起到事半功倍的效果。
二、数位 DP 简介
1、数位 DP 定义
- 数位 DP 又称 数位动态规划,一般可以从题干就能确定这个题是否可以用 数位 DP 来求解。因为 数位 DP 的题目一般都描述成如下两种问法之一:
【问法1】给定一个闭区间 [ l , r ] [l, r] [l,r],求在这个区间中,满足 某些条件 的数的个数。
【问法2】如果一个数字满足 某些条件,则称之为 X X X 数,给定闭区间 [ l , r ] [l, r] [l,r],求这个区间中 X X X 数的个数。
- 这里的 某些条件 决定了状态转移的决策,这样说或许比较抽象,那么接下来,我们通过一个简单的例题来了解下 数位DP 的一般求解过程。
2、数位 DP 引例
【例题1】如果一个数的所有位数加起来是 10 10 10 的倍数, 则称之为 g o o d n u m b e r good \\ number good number,求区间 [ l , r ] ( 0 ≤ l ≤ r ≤ 1 0 18 ) [l, r](0 \\le l \\le r \\le 10^{18}) [l,r](0≤l≤r≤1018) 内 g o o d n u m b e r good \\ number good number 的个数;
- 对于这个问题,朴素算法就是枚举区间里的每个数,并且判断可行性,时间复杂度为 o ( ( r − l ) c ) o((r-l)c) o((r−l)c), c = 19 c=19 c=19,肯定是无法接受的。
1)差分转换
- 对于区间 [ l , r ] [l, r] [l,r] 内求满足数量的数,可以利用差分法分解问题;
- 假设
[
0
,
x
]
[0, x]
[0,x] 内的
g
o
o
d
n
u
m
b
e
r
good \\ number
good number 数量为
g
x
g_x
gx,那么区间
[
l
,
r
]
[l, r]
[l,r] 内的数量就是
g
r
−
g
l
−
1
g_r - g_{l-1}
gr−gl−1;分别用同样的方法求出
g
r
g_r
gr 和
g
l
−
1
g_{l-1}
gl−1,再相减即可;
图二-2-1
2)数位性质
- 如果一个数字 i i i 满足 i < x i < x i<x,那么 i i i 从高到低肯定出现某一位,使得这一位上的数值小于 x x x 对应位上的数值,并且之前的所有高位都和 x x x 上的位相等。
- 举个例子,当 x = 1314 x = 1314 x=1314 时, i = 0 a b c i=0abc i=0abc、 i = 12 a b i=12ab i=12ab、 i = 130 a i=130a i=130a、 i = 1312 i=1312 i=1312,那么对于 i i i 而言,无论后面的字母取什么数字,都是满足 i < x i < x i<x 这个条件的。
- 如图二-2-2所示:
图二-2-2 - 如果我们要求 g 1314 g_{1314} g1314 的值,可以通过枚举高位:当最高位为0,那么问题就转化成 g 999 g_{999} g999 的子问题;当最高位为1,问题就转化成 g 314 g_{314} g314 的子问题。
- g 314 g_{314} g314 可以继续递归求解,而 g 999 g_{999} g999 由于每一位数字范围都是 [ 0 , 9 ] [0,9] [0,9],可以转换成一般的动态规划问题来求解。
3)前缀状态
- 这里的前缀状态就对应了之前提到的 某些条件;
- 在这个问题中,前缀状态的描述为:一个数字前缀的各位数字之和对10取余(模)的值。
- 前缀状态的变化过程如图二-2-3所示:
图二-2-3 - 在【例题1】中,前缀状态的含义是:对于一个数 520 ,我们不需要记录 520 ,而只需要记录 7;对于 52013,我们不需要记录 52013,而只需要记录 1。这样就把原本海量的状态,变成了最多 10 个状态。
3、状态分析
1)状态定义
- 根据以上的三个信息,我们可以从高位到低位枚举数字 i i i 的每一位,逐步把问题转化成小问题求解。
- 我们可以定义 f ( n , s t , l i m ) f(n, st, lim) f(n,st,lim) 表示剩下还需要考虑 n n n 位数字,前面已经枚举的高位组成的前缀状态为 s t st st,且用 l i m lim lim 表示当前第 n n n 位是否能够取到最大值(对于 b b b 进制,最大值就是 b − 1 b-1 b−1,比如 10 进制状态下,最大值就是 9) 时的数字个数。我们来仔细解释一下这三维代表的含义:
- 1)当前枚举的位是 n n n 位, n n n 大的代表高位,小的代表低位;
- 2)
s
t
st
st 就是前缀状态,在这个问题中,代表了所有已经枚举的高位(即数字前缀)的各位数字之和对10取余(模)。注意:我们并不关心前缀的每一位数字是什么,而只关心它们加和模10之后的值是什么。
图二-3-1 - 3) l i m = t r u e lim=true lim=true 表示的是已经枚举的高位中已经出现了某一位比给定 x x x 对应位小的数,那么后面枚举的每个数最大值就不再受 x x x 控制;否则,最大值就应该是 x x x 的对应位。举例说明,当十进制下的数 x = 1314 x = 1314 x=1314 时,枚举到高位前三位为 “131”, l i m = f a l s e lim = false lim=false, 那么第四位数字的区间取值就是 [ 0 , 4 ] [0,4] [0,4];而枚举到高位前三位为 “130” 时, l i m = t r u e lim = true lim=true,那么第四位数字的区间取值就是 [ 0 , 9 ] [0, 9] [0,9]。参考 图二-2-2 加深理解。
2)状态转移
- 所以,我们根据以上的状态,预处理 x x x 的每个数位,表示成十进制如下:
- x = d n d n − 1 . . . d 1 x = d_nd_{n-1}...d_1 x=dndn−1...d1
- (其中 d n d_n dn 代表最高位, d 1 d_1 d1 代表最低位)
- 得出状态转移方程如下:
- f ( n , s t , l i m ) = ∑ k = 0 m a x v f ( n − 1 , ( s t + k ) m o d 10 , l i m o r ( k < m a x v ) ) \\begin{aligned}& f(n, st, lim) \\\\ &= \\sum_{k=0}^{maxv} f(n-1, (st+k) \\mod 10, lim \\ or \\ (k < maxv))\\end{aligned} f(n,st,lim)=k=0∑maxvf(n−1,(st+k)mod10,lim or (以上是关于夜深敲代码——记录一个优化过程的主要内容,如果未能解决你的问题,请参考以下文章