关于acm:XCPC真题1Bits-Reverse-Empty-Squares-Wall-Painting

作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1147.htmlA. Bits Reverse题目链接:https://codeforces.com/gym/102823/problem/D 题意给定两个数字x和y,当初有一种操作:将y的二进制数的间断三位进行翻转。问起码操作多少次能够使得x = y,或输入-1示意无奈使得x = y。 剖析因为每次固定选间断三位进行翻转,咱们能够想一下间断三位翻转的一些性质。 首先两头这个数字必定是不变的,而后两边的数字swap一下。 也就是每次会抉择间隔为2的两个数字进行一次替换,那么如果某个1在奇数位上,替换后仍然在奇数位上,如果在偶数位上,替换后仍然在偶数位上。 如下图,红色区域的二进制数和蓝色区域的二进制数是互相独立的。 所以咱们能够进行奇偶分类的探讨,求出在奇数位上所需的操作次数,而后再求在偶数位上所需的操作次数即可。 咱们能够发现,如果要使得操作后x = y,那么对于奇数位(即上图中的蓝色区域),y最高位的1肯定对应x最高位的1,以此类推。而在奇数位上挪动一格须要的操作次数是1次。 具体看下图的对应关系: 代码#include <bits/stdc++.h>#define int long longusing namespace std;const int N = 105;int a[N], b[N], pa, pb;int kase;int getabs(int x){return x < 0 ? -x : x;}void solve(){ cout << "Case " << ++ kase << ": "; int x, y;cin >> x >> y; pa = pb = 0; int ans = 0; for(int i = 0;i < 64; i += 2) { if(x >> i & 1)a[++ pa] = i; if(y >> i & 1)b[++ pb] = i; } if(pa != pb) { cout << -1 << '\n'; return; } for(int i = 1;i <= pa; ++ i)ans += getabs(a[i] - b[i]) / 2; pa = pb = 0; for(int i = 1;i < 64; i += 2) { if(x >> i & 1)a[++ pa] = i; if(y >> i & 1)b[++ pb] = i; } if(pa != pb) { cout << -1 << '\n'; return; } for(int i = 1;i <= pa; ++ i)ans += getabs(a[i] - b[i]) / 2; cout << ans << '\n';}signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int _;cin >> _; while(_ --)solve(); return 0; }B. Empty Squares题目链接:https://codeforces.com/gym/104252/problem/E ...

May 5, 2023 · 3 min · jiezi

关于acm:ACM算法竞赛日常训练DAY7题解与分析约数个数的和HAOI2011向量-整除分块-扩展欧几里得

DAY7共2题: 约数个数的和(和式变换,整除分块)[HAOI2011]向量(扩大欧几里得剖析)难度适中。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 原文链接(浏览原文取得更好浏览体验):https://www.eriktse.com/algorithm/1098.html约数个数的和题目传送门:https://ac.nowcoder.com/acm/problem/14682 剖析题意咱们能够晓得答案表达式如下: $$ans=\sum_{i=1}^{n}\sum_{d|i}1$$ 其中 $d|i$ 示意d能够整除i,这个柿子的意思是枚举每一个i,而后枚举i的所有约数,找到一个约数,就把答案+1。然而咱们发现枚举约数是一个不太不便的事儿,咱们能够思考变换求和的指标。 $$ans=\sum_{i=1}^{n}\sum_{d=1}^{n}[d|i]$$ 其中 [expression] 是一个布尔函数,当中括号外面的表达式为真时后果为1,反之为0。然而这样的柿子没有实质的扭转,咱们能够替换求和秩序。 $$ans=\sum_{d=1}^{n}\sum_{i=1}^{n}[d|i]$$ 当初咱们能够发现除了d的枚举这一层,剩下的的局部能够进行整除分块,它表白的含意是在d曾经给定的状况下,在[1, n]中有多少个d的倍数。 所以再次变换: $$ans = \sum_{d=1}^{n}\lfloor\frac{n}{d}\rfloor$$ 复杂度为$O(\sqrt{n})$。 对于整除分块的材料:https://oi-wiki.org/math/number-theory/sqrt-decomposition/ #include <bits/stdc++.h>#define int long longusing namespace std;signed main(){ int n;scanf("%lld", &n); int ans = 0; for(int l = 1, r;l <= n; l = r + 1) { r = n / (n / l); ans += (n / l) * (r - l + 1); } printf("%lld\n", ans); return 0;}[HAOI2011]向量题目传送门:https://ac.nowcoder.com/acm/problem/19985 ...

March 31, 2023 · 1 min · jiezi

关于acm:ACM算法竞赛日常训练DAY3题解与分析旅游tokitsukaze-and-Soldier

DAY3共2题: 游览tokitsukaze and Soldier 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 原文链接(浏览原文取得更好浏览体验):https://www.eriktse.com/algorithm/1093.html游览题目传送门:https://ac.nowcoder.com/acm/problem/15748 该题次要考查对树的了解,以及简略的树上dp和贪婪算法。 咱们将会住的节点标记为1,其余不住的节点标记为0。 咱们能够发现,根节点(s)是肯定会标记为1的,那么剩下的节点该怎么调配能够使得标记为1的节点数最多呢? 当咱们在某个点x标记时,咱们能够发现它的父亲、儿子们都不能再被标记了。然而点x的兄弟却不受影响,接下来考虑一下哪些节点的兄弟多呢?应该是叶子节点。 所以咱们能够想到首先将根节点和叶子结点全副都标记为1,而后遍历整棵树,如果某个点的父亲和儿子们都没被标记,那么他也能够被标记为1。 留神思考非凡状况,比方只有一个点的树,只有两个点的树....#include <bits/stdc++.h>#define int long longusing namespace std;const int maxn = 5e5 + 9, inf = 8e18;bitset<maxn> sel;vector<int> g[maxn];int n, s;void dfs(int x, int pre){ //如果到了叶子节点,就间接标记并返回 //这里sel[x] = !sel[pre]是思考到叶子的深度可能为2(即叶子的父亲就是根) //此时根肯定被标记,那么叶子就不能被标记 //如果是个别状况,那么父亲必定不会被标记(因为父亲的标记须要儿子解决实现之后再决定) //本人就打上标记 if(g[x].size() == 1 and x != s)return sel[x] = !sel[pre], void(); bool tag = true;//tag == true示意以后点能够被标记 if(x == s)sel[x] = true;//根肯定被标记 else if(sel[pre])tag = false;//如果父亲被标记了,那么以后点肯定不能被标记 //看看儿子们是否被标记 for(auto &y : g[x]) { if(y == pre)continue; dfs(y, x); if(sel[y])tag = false; } sel[x] = tag;}signed main(){ scanf("%lld %lld", &n, &s); for(int i = 1;i < n; ++ i) { int x, y;scanf("%lld %lld", &x, &y); g[x].push_back(y), g[y].push_back(x); } dfs(s, 0); int ans = 0; for(int i = 1;i <= n; ++ i) if(sel[i])ans ++;//统计标记的点的个数 printf("%lld\n", ans); return 0;}做完这道题,咱们能够总结一点点对于树上dp这一类题的教训技巧: ...

March 26, 2023 · 2 min · jiezi

关于acm:牛客小白月赛69题解与分析AF蛋挞玩具开题顺序旅游等腰三角形easy等腰三角形hard

较量传送门:https://ac.nowcoder.com/acm/contest/52441 感觉整体难度有点偏大。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.comA-蛋挞签到题。 只需比拟a / b和a % b的大小即可。留神开longlong。 #include <bits/stdc++.h>#define int long longusing namespace std;signed main(){ int a, b;scanf("%lld %lld", &a, &b); if(a / b < a % b)printf("niuniu eats more than others"); else if(a / b > a % b)printf("niuniu eats less than others"); else printf("same"); return 0;}B-玩具排序贪婪。 因为咱们要将n个玩具全副买下,所以咱们免单的玩具价格越高越好,咱们将整个数组排升序后从后往前两个两个拿,且只付更高价格的玩具的钱。 #include <bits/stdc++.h>#define int long longusing namespace std;const int maxn = 1e6 + 9;int a[maxn];signed main(){ int n;scanf("%lld", &n); for(int i = 1;i <= n; ++ i)scanf("%lld", a + i); sort(a + 1,a + 1 + n); int ans = 0; for(int i = n;i >= 1; -- i) { ans += a[i]; i --; } printf("%lld\n", ans); return 0;}C-开题程序dfs。 ...

March 25, 2023 · 4 min · jiezi

关于acm:ACM算法竞赛日常训练DAY2题解与分析比赛数学考试简单瞎搞题

DAY2共三题: 较量(概率)数学考试(前缀和与思维)简略瞎搞题(dp)视频解说:https://www.bilibili.com/video/BV1hP411o7RD/ 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.com较量tag: 概率传送门:https://ac.nowcoder.com/acm/problem/14734 咱们设对于每一道题: 事件A:本人做对了事件B:听右边的,做对了事件C:听左边的,做对了三个事件的概率都曾经给出了,别离是$P(A), P(B), P(C)$。 设事件Y:该题做对了 咱们能够晓得事件$Y=A \cup B \cup C$,如果要正向求解$Y$是有肯定难度的,因为这里的$Y$实际上由7个最小项形成,而事件$\overline{Y}$仅有一个最小项形成(这里就不开展说了)。 容易失去表达式: $$P(Y) = 1 - (1-P(A))(1-P(B))(1-P(C))$$ 而后咱们能够枚举所有的序列,而后算一下以后这个情景对答案的奉献。 枚举序列能够用二进制数枚举,也能够用dfs,用dfs的话有个益处就是能够剪枝。 二进制枚举可能常数小点。 #include <bits/stdc++.h>#define int long longusing namespace std;const int maxn = 20;double a[maxn], b[maxn], c[maxn];double ans[maxn];double getp(int k){ return max(0.0, 1.0 - (1 - a[k]) * (1 - b[k]) * (1 - c[k]));}void dfs(int dep, int cnt, double p){ if(p == 0)return; if(dep == 13) { ans[cnt] += p; return; } //没对第i题 dfs(dep + 1, cnt, p * (1.0 - getp(dep))); //对了第i题 dfs(dep + 1, cnt + 1, p * getp(dep));}signed main(){ for(int i = 1;i <= 12; ++ i)scanf("%lf", a + i); for(int i = 1;i <= 12; ++ i)scanf("%lf", b + i); for(int i = 1;i <= 12; ++ i)scanf("%lf", c + i); //for(int i = 1;i <= 12; ++ i)printf("p[%lld] = %.2lf\n", i, getp(i)); dfs(1, 0, 1); for(int i = 0;i <= 12; ++ i)printf("%.6f\n", ans[i]); return 0;}数学考试tag: 前缀和,思维传送门:https://ac.nowcoder.com/acm/problem/15553 ...

March 24, 2023 · 2 min · jiezi

一维树状数组详解(萌新的第一篇博客)

(萌新第一次发文,请大佬指正)要了解树状数组,首先需要了解它是用来做什么的.那么:树状数组的问题模型单点维护,区间查询(PUIQ问题)区间维护,单点查询(IUPQ问题)求逆序对问题先来了解一下树状数组的逻辑模型如图:突然一看可能难以理解,那么是什么把它们联系起来的呢?接下来介绍lowbit函数lowbit(求二进制数最后一位1的位置)这里用到了补码的原理:即负数的补码为其二进制绝对值取反+1,而当其与其绝对值取&操作时得到的恰好就是其二进制最后一位1的位置例如:00001000(8)的负数补码为11111000(-8) 与操作后为00001000而lowbit(8)的值就为 00001000;则 lowbit函数代码inline int lowbit(int x){ return x & -x;}这里就稍微偏个题顺便介绍一下inline(内联函数)C++关键字,在函数声明或定义中函数返回类型前加上关键字inline,即可以把函数指定为内联函数。在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。此关键字仅是对编译器的一种建议,并非强制.对于内容较短且无循环的代码,inline的使用可以增加函数运行的效率,但其效率的增加是以代码长度为代价,所以,仅在短且无循环的函数前可以使用,其他情况避免使用.原理那么lowbit函数的用处是什么呢.让我们以二进制思维稍微注意一下可以发现,每个序号的二进制数的后导0的个数,恰为其包含的数组的深度(自底向上的),设其为d,那么其包含的范围就是包括它向前的2^d个数.如100(4)为从100向前4个原数组中的数的和,而110(6)是从110向前2个原数组中的数的和.我们令原数组为a[],树状数组为tree[].这时,我们可以理解tree数组中存储的到底是什么数据,那么对于以上问题模型,我们该如何求解呢.更新(update函数)我们要更新原数组中某一个数,那么就得更新树状数组中所有包含它的数据,那么,都有哪组数据包含它呢,也就是寻找这个子节点的所有根节点的特点.这时,就又轮到我们的lowbit函数上场了,x的所有父节点只需x + lowbit(x)就可以得到.那么update函数的代码(时间复杂度O(logn))://x为需要修改的节点,v为a[x]增加的值,n是树状数组的最大范围.void update(int x,int v){ for(;x <= n;x += lowbit(x)) tree[x] += v;}查询(query函数)对于树状数组tree,我们可以了解其存储的值实质上是某一区间的前缀和.我们查询某一段区间(x,y)的值,就只需要求出y的前缀和减去x-1的前缀和这样得到的,就是区间(x,y)的和.那么(1,x)区间的前缀和怎么求呢,仍然是lowbit函数,只需要做一遍更新的逆过程,即计算tree数组(x->1)的和就可以求出所有不重合的区间前缀和.为什么tree(x->1)就是该区间的前缀和?在二进制的视角下,x的数位上每一个1,都代表其一段域内的值,那么,每个1都必然与其他1的范围不重合例如:tree[110] = tree[100] + tree[10]如此,query的代码就很明了了.int query(int x){ int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res;}板子题链接(luoguP3374)到此为止,我们讲解的就是PUIQ模型,即点更新,段查询.怎么样代码是不是短到怀疑人生,这在赛场上写起来不是舒舒服服?那么,接下来的IUPQ模型,就需要一个切入点来完成操作了.IUPQ模型的解题切入点—差分当我们想要进行段更新时,那么如果仍然用上面的代码,就不得不添加一个for循环,此时update函数的时间复杂度会上升到O(nlogn),那么,有没有什么方法来优化操作呢?解决方案就是–差分差分,也就是定义一个差分数组b来存储a数组的差值,而tree作为数组b的树状数组.即:b[1] = a[1]b[2] = a[2] - a[1]b[4] = a[4] - a[1]tree[1] = b[1]tree[2] = b[2] + tree[1]tree[4] = tree[2] + tree[3] + b[4]那么这时的a数组值该如何得到呢?对于b数组,a[n]就等于b[1] +…+b[n]而query函数刚好就是用来求b[n]的前缀和,也就是a[n]的值.那么更新操作呢,对于差分数组b,若我们想要更新[l,r]范围内的值+v,那么我们只需要将b[l]更新为b[l] + v就代表着a[l] - a[n]所有数都+1,而在b[r+1]处将+v的效果消除,即b[r+1] - v那么如何将这个操作更新在tree数组中,就是上文update的事情了.即只需更新b[l]+v,b[r+1]-v即可update(l,v);update(r+1,-v);复杂度同样为O(logn)板子题链接(luoguP3368)求逆序对问题问题模型,给定n个整数,求出逆序整数对数(即a[u] > a[v] && u < v的整数对)看见题目我们就可以写出O(n2)的暴力for来求解,但是如何将其用树状数组来优化到O(nlogn)呢这里用到桶排序的思想.首先让问题简单化一点,让n个整数a[i]均小于等于100.那么我们就可以开tree[105]的数组,每次读入更新一个数时,只需update(a[i],1)此时,比a[i]大的数字就是需要更新的逆序对对数,即query(a[100]) - query(a[i])因为当前共读入了i个数,那么求值亦可简化为i - query[a[i]]也就是读入一轮+每次查询就可得到总共的逆序对对数.时间复杂度O(nlogn)那么当数据足够大时,我们的数组存不下的时候呢,这时候,就轮到我们的核心思想,离散化出场了.对于离散化,个人理解就是由于想要利用桶数组,故将数据相对缩小(保证相对大小不变)到可以开到的数组那么大而减小空间需求.那么到底该如何实现,请看如下代码:离散化核心代码:struct node{ int v;//数值本身 int order;//原序列的的下标}a[500005];int dis[500005]; //用来存储原数第i个数的order下标是什么sort(a,a+n,cmp); //注意需要由大到小排for(int i = 1;i <= n;++i) dis[a[i].order] = i;原理很简单就只是按a[i].v的大小重排,并重新赋予他们相对大小不变,整体缩小的新的a[i].v具体代码如下:(HDU2689)#include <bits/stdc++.h>#define mem(n) memset(n,0,sizeof(n))using namespace std;int n;struct node{ int val,order; bool operator < (const node & x) const{ return this->val < x.val; } }a[1005];int dis[1005]; //差分数组int tree[1005];inline int lowbit(int x){ return x & -x;}void update(int x,int v){ for(;x <= n;x +=lowbit(x)) tree[x] += v;}int query(int x){ int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res;}int main(){ while(~scanf("%d",&n)){ mem(a); mem(dis); mem(tree); for(int i = 1;i <=n;++i){ scanf("%d",&a[i].val); a[i].order = i; } sort(a + 1, a + 1 + n); for(int i = 1;i <= n;++i){ dis[a[i].order] = i; } int cnt = 0; for(int i = 1;i <= n;++i){ update(dis[i],1); cnt += i - query(dis[i]); } printf("%d\n",cnt); } return 0;} ...

April 10, 2019 · 1 min · jiezi

自我感觉良好的大数模拟

从前的大数模拟都是自己用字符串来敲的,突然看到同学的代码后释放了很多思维,不仅敲得快还不容易错,于是找个晚上敲下大数的一些模拟,留下板子记录一下.对于负数加减模拟运算我们可以分情况讨论并编写另一个函数,即:若两数均正 ans = add(a,b)若两数均负 ans = ‘-’ + add(a,b)若一正一负 ans = sub(a,b) or ans = sub(b,a)可拟函数string add_op(string a,string b){ string ans; if(a[0] == ‘-’ && b[0] == ‘-’){ a.erase(a.begin()); b.erase(b.begin()); ans = ‘-’ + big_add(a,b); } else if(a[0] == ‘-’){ a.erase(a.begin()); ans = big_sub(b,a); } else if(b[0] == ‘-’){ b.erase(b.begin()); ans = big_sub(a,b); } else{ ans = big_add(a,b); } return ans;}大数加法//不支持负数const int MAXN = 1e5 + 10;int t[MAXN];string big_add(string a,string b){ int len1 = a.size(); int len2 = b.size(); reverse(a.begin(),a.end()); //调换顺序方便处理 reverse(b.begin(),b.end()); if(len1 < len2){ swap(a,b); //令a是数位较长的数 swap(len1,len2); } for(int i = 0;i < len2;++i){ //t数组存在第i位上两数相加的结果,暂时不进位 t[i] = a[i] -‘0’ + b[i] - ‘0’; } for(int i = len2;i < len1;++i){ t[i] = a[i] - ‘0’; } int flag = 0; //flag 标志是否需要进位 for(int i = 0;i < len1;++i){ if(flag) { t[i]++; flag = 0; } if(t[i] >= 10){ t[i]-=10; flag = 1; } } if(flag) t[len1] = 1; //处理最高位是否需要进位,若不需要令最高位为 0 else t[len1] = 0; string ans; flag = 0; for(int i = len1;i >= 0 ;–i){ //去除前导0 if( flag == 0 && t[i] == 0) continue; flag = 1; ans.push_back(t[i] + ‘0’); } return ans;}大数减法const int MAXN = 1e8 + 10;int t[MAXN];string big_sub(string a,string b){ string ans; int pos = 0; //标记结果是否为负数 if(a.size() < b.size() ||( a < b && a.size() == b.size())) { swap(a,b); pos = 1; } int len1 = a.size(); int len2 = b.size(); reverse(a.begin(),a.end()); //调换顺序便于处理 reverse(b.begin(),b.end()); for(int i = 0;i < len2 ;++i){ //t数组保存第i位上两数之差 t[i] = a[i] - b[i]; } for(int i = len2;i < len1;++i){ t[i] = a[i] - ‘0’; //若a位数较长,则长度超过b的位结果为a[i] - 0 } int flag = 0; for(int i = 0;i < len1;++i){ //借位 if(flag){ flag = 0; t[i]–; } if(t[i] < 0){ t[i]+=10; flag = 1; } } flag = 0; for(int i = len1-1;i >= 0;–i){ //去除前导0 if(!flag && t[i] == 0) continue; flag = 1; ans.push_back(t[i] + ‘0’); } if(ans.empty()) ans.push_back(‘0’); //若结果为0,答案 ans = 0 if(pos && ans[0] != ‘0’) ans = ‘-’ + ans; return ans;}大数乘法int t[10000000];string mul(string a,string b){ reverse(a.begin(),a.end()); //交换顺序,方便计算 reverse(b.begin(),b.end()); int len1=a.size(); int len2=b.size(); for(int i = 0;i < len1;++i) for(int j = 0;j < len2;++j) t[i+j]=(a[i]-‘0’)(b[j]-‘0’)+t[i+j]; //先整体乘起来,不进位 int over=0; for(int i = 0;i < len1+len2;++i) //进位 { t[i]+=over; over=0; if(t[i]>=10) { over=t[i]/10; t[i]%=10; } } string c; for(int i = 0;i < len1+len2;++i) { c+=(t[i]+‘0’); } return c;}大数求模//大数求模就很简单了,根据同余定理来按位求模就好了long long big_mod(string a,long long mod){ int len=a.size(); long long ans=0; for(int i=0;i<len;++i) ans=(ans10%mod+a[i]-‘0’)%mod; return ans;}大数求幂//还不太会,先留个板子(留坑)#include<bits/stdc++.h>using namespace std;const int mod = 1e9 + 7;typedef long long ll;ll phi(ll n) { int ans = n, temp = n; for (int i = 2; i*i <= temp; i++) { if (temp%i == 0) { ans -= ans / i; while (temp%i == 0) temp /= i; } } if (temp > 1) ans -= ans / temp; return ans;}ll mod_pow(ll x, ll n, ll mod) //快速幂 { ll ans = 1; while (n) { if (n % 2 == 1) ans = ans * x%mod; x = x * x%mod; n /= 2; } return ans;}char a[1000010],b[1000010];int main(){ scanf("%s%s", a, b); ll phic = phi(mod); int i, len = strlen(a); ll res = 0, ans; for (i = 0; i < len; i++) { res = res * 10 + a[i] - ‘0’; if (res > phic) break; } if (i == len) { ans = mod_pow(2, res, mod) % mod; } else { res = 0; for (int i = 0; i < len; i++) { res = res * 10 + a[i] - ‘0’; res %= phic; } ans = mod_pow(2, res + phic, mod) % mod; } cout << ans << endl; return 0;} ...

March 16, 2019 · 3 min · jiezi

如何像程序员一样思考 - 解决问题的经验与教训

如果你对编程感兴趣,你可能看过这句话:“这个国家的每个人都应该学习计算机编程,因为它会教你思考。” - Steve Jobs你很可能想知道这句话是什么意思?以及如何做到?本质上讲,这句话是关于更高效解决问题的方法。在这篇文章中,我的目标是教会你这种方法。读完本文,你将明确知道要采取哪些步骤来成为更好的问题解决者。为什么这很重要?解决问题是元技能。我们都面临问题。大的和小的。有时,我们处理它们的方式,呃……很随意。你需要有一个系统方法,这可能是你“解决”问题的方式(我开始编程时就是这么做的):尝试一个方案。如果这不起作用,请尝试另一个。如果还不起作用,请重复步骤2直到解决。可能你运气好解决了问题。但这是最糟糕方法!浪费大量的时间。最佳方法是:a)有一个框架, b)练习掌握这个框架。“几乎所有雇主都优先考虑解决问题的技能。相比编程语言的熟练程度、调试和系统设计,解决问题的技能几乎是雇主寻求的最重要的技能。展示计算思维或将大型、复杂问题拆分的能力与工作所需的基线技能一样有价值(如果不是更多)。” - Hacker Rank(2018年开发人员技能报告)拥有一个框架为了找到合适的框架,我参考了Tim Ferriss关于学习的书《The 4-Hour Chef》中的建议。这让我采访了两个人,他们非常令人印象深刻:C.Jordan Ball(在Coderbyte的65000多名用户中排名第一或第二)和V.Anton Spraul(《像程序员一样思考:解决创造性问题导论》一书的作者“)。我问他们同样的问题,猜猜结果如何? 他们的回答非常相似!很快,你也会认识他们。旁注:这并不意味着他们对待每件事都用同样的方式。每个人都是不同的,你也和大家不一样。但是如果你遵从我们都认可的原则,你会更快进步。“我看到新程序员犯下的最大错误就是专注于学习语法,而不是学习如何解决问题。” - V.Anton Spraul那么,当遇到新问题你该应该怎么做?下面是步骤:1. 理解你的问题明确被问的问题是什么。大多数问题很难是因为你不理解它们(因此这是第一步)。如何确定你理解了问题?当你能用简单的语言准确描述它,你就理解了这个问题了。你还记得曾经被困在一个问题上,你尝试描述它,却立即发现之前没有考虑到的逻辑漏洞?大多数程序员都知道这种感觉。这就是为什么你应该写下你的问题、画画涂鸦,或告诉别人你的问题(或者有些人使用橡皮鸭调试法)。“如果你不能用简单的术语来解释某事,那你还没理解它。” - Richard Feynman2. 做好计划没有计划前就不要开始解决问题。你需要计划你的解决方案。如果你不能写下明确的步骤,别人就没法帮你。在编程中,这意味着不要立即开始hacking。给大脑时间来分析问题和处理信息。要想获得一个好的计划,请回答这个问题:“给定输入X,返回输出Y所需的步骤是什么?”3. 分割问题请注意,这是最重要的一步。不要试图解决一个大问题,你会哭的。相反,将其分解为子问题。这些子问题更容易解决。然后,逐个解决每个子问题。从最简单的开始。最简单意味着你知道答案(或者很接近答案),还意味着要解决的这个子问题不依赖于其它问题。一旦解决了每个子问题,请连接所有“子解决方案”,你就得到原始问题的解决方案了。恭喜!这种方法是解决问题的基石。务必记住它(如果有必要,这个步骤要多读几遍)。“如果我能教会每个初学程序员解决问题的技巧,那就是’减少问题的技巧性’。例如,假设你是一名程序员新手,被要求编写一个程序:读取十个数字,确定第三大的数字。对于一个全新的程序员来说,这可能是一个艰难的任务,即使它只需要基本的编程语法。如果你遇到困难,你应该把问题简化为更简单的问题。找到最大的那个数,而不是第三大的数字。还是太难了?那找到三个数字中最大的一个呢?或者两个数中较大的一个?将问题简化到你知道如何解决,然后写下解决方案。然后稍微扩展问题并重写解决方案以匹配,并继续扩展直到你回到起点。“ - V.Anton Spraul4. 卡住了?到现在为止,你可能正坐在那里思考“嘿理查德……这很酷,但是如果我被困住,甚至无法解决一个子问题怎么办?”首先,深吸一口气。其次,这很公平。因为每个人都会遇到这个情况!不同之处在于,最好的程序员/问题解决者面对bug/错误时,他们很感兴趣而不是恼火。事实上,面对打击时可以尝试以下三件事:调试逐步执行你的解决方案,尝试找到出错的地方。程序员称这为调用(事实上,这都是调试器做的)。“调试的艺术是弄清楚你真正告诉程序要做什么,而不是你认为你告诉它要做的事情是什么。” - Andrew Singer重新评估退后一步,换个角度看问题。是否有地方可以被抽象为更一般的方法?“有时我们会在问题的细节上迷失方向,而忽略了在更一般的层面上解决问题这个原则。当然,这个经典的例子是连续整数求和,1 + 2 + 3 + … + n,非常年轻的高斯很快就认识到结果是n(n + 1)/ 2,从而避免了冗余计算。“ - C.Jordan Ball旁注:另一种重新评估方式是重新开始。删除所有内容,然后重新开始。我是认真的,你会惊讶于这个方法很有效。研究啊,尝试谷歌。不管你遇到什么问题,有人可能已经遇到并解决了,你要找到那个人/解决方案。事实上,即使你已经解决了问题,也要这样做!(你可以从其他人的解决方案中学到很多东西)。警告:不要寻找解决这个大问题的方法,只寻找子问题的解决方案。为什么? 因为除非你挣扎(甚至一点点),否则你将无法学到任何东西。如果你什么都不学,那就浪费了你的时间。练习不要指望练习一周后就变得更好。如果你想成为一个好的问题解决者,你需要解决很多问题!实践。实践。实践。在你意识到“这个问题可以通过某个方法解决前”,你需要大量时间来练习。如何练习?你有很多问题可以选择:国际象棋谜题、数学问题、数独、围棋、大富翁、视频游戏、加密……事实上,成功人士的共同点是他们有练习“解决微观问题”的习惯。例如,Peter Thiel下棋、Elon Musk玩视频游戏。“Byron Reeves说:‘如果你想看看三到五年里的商业领导力是什么样的,那就看看在线游戏中正在发生什么。‘回到今天,Elon Musk、Reid Hoffman、Mark Zuckerberg和其他许多人都认为游戏是他们在建立公司方面取得成功的基础。” - Mary Meeker(2017年互联网趋势报告)这是否意味着你应该只玩视频游戏?当然不。但是视频游戏到底带给人们什么?解决问题!所以,你应该做的是找到练习的方法。可以让你解决许多微观问题的东西(理想情况下,是一些你喜欢的东西)。例如,我喜欢编程挑战。每天,我都尝试解决至少一个问题(通常在Coderbyte上)。就像我说的,所有问题都有相似的模式。结论现在,你更清楚理解什么是“像程序员一样思考”。你也意识到解决问题的能力是一项需要培养的令人难以置信的技能(元技能)。这好像这还不够,请记住如何练习解决问题的能力!“就在你认为自己已成功驾驭一个问题时,另一个问题出现了。但生活也因此而变得有趣。生活是突破这些障碍的过程 - 我们也必须突破。每一次,你都会学到一些。每一次,你都锻炼力量、智慧和观点。每一次,竞争会减少一点,直到最后剩下的就是你:最好的你。” - Ryan Holiday(《The Obstacle is the Way》)现在,去解决问题吧,祝你好运。译者说本文以“像程序员一样思考”为题,介绍了如何成为合格、优秀的程序员。作者结合两位顶尖程序员的回答、参考一些关于学习的书籍,认为最重要的是培养解决问题的能力/技能,分享了培养这个能力的方法:形成思维框架,然后在实践中不断练习。笔者理解为多刷OJ。说明原文链接翻译:@AdolphLWQ项目地址tt:自动生成翻译模板用时: 3h (人机混合)

January 13, 2019 · 1 min · jiezi