cf交互题怎么写||算法学习方法论||codeforces700ABC总结 cf||引用大佬的博客||开篇
最近几周收集了一些学习算法的方法论,用博客记录下来,同时也记录自己的行动过程,最后把自己认可的声音传递出来。
首先是namomo spring camp 2022群文件里的《一种练习编程竞赛的方法》,里面主要讨论如何冲cf的榜。对不同水平的“代码器”给出不同建议,由于版权或者这篇文章出现太多诸如“coder==代码器”的神奇翻译以致读得折磨,这里也凭印象复述其中的内容。
1、1400以前,处于这阶段的coder痛点在对语言的熟练度,只要刷刷基本题就可以跃过这里。
2、1400-1900,这阶段的coder需要学习十种基本专题,熟练掌握后即可突破1800。其中有些强行翻译成中文的我还无法领悟,连蒙带猜的有如下:
暴力、贪心、动态规划、搜索、地杰斯特拉。高炉、二进制索引树 nCr、 npr 模组反向 比特面具 二进制搜索。
3、1900-2200,在上述十个专题的基础上,还要非常快速的切题,具有数学思考能力。
4、2200-2400,上述技能+div1中的百人题,到atcoder上做思维题。
接着是,前辈的blog,关于怎么写题解。1、概括题面;2、分析过程;3、贴个代码。
作者:XLor
链接:https://www.zhihu.com/question/316758023/answer/633089846
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
那么,结合以上方法,我想尝试从cfround700-div2开始倒着刷ABC水题,慢慢有意识地体会他们给的建议。
round700-div2
A
1、描述
在这种博弈规则下,给一条小写字符串,alice和bob先后轮流去改变没改过的位置上的字母,直到所有字母都改过一次,alice要让最终的字符串字典序最小,bob要使之最大。问最后串串是什么?Alice先手,不能改成相同的字母(不能不改)。
2、分析
每个位置只改一次,而且在字典序问题中最高位越小,整个串串就越小,所以他们每次都会选择最前面的没改过的位置的字母。其次,不能改成相同字母,那么,对alice来说,除a以外都可以改成a,a只能改成b,bob与之相似。所以,最后的算法就是,遍历一遍字符串,第奇数位改最小,第偶数位改最大。
3、代码:
#include<iostream> #include<stdio.h> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; //#define int long long #define _for(i,a,b) for(int i=(a);i<=(b);i++) #define _rp(i,b,a) for(int i=(b);i>=(a);i--) #define mst(f,a) memset(f,a,sizeof(f)) #define close std::ios::sync_with_stdio(0) typedef long long ll; const int N=2e5+7; int n,t; string s; signed main(){ cin>>t; while(t--){ cin>>s; _for(i,0,s.size()-1){ if(i%2==0){ if(s[i]!='a')cout<<'a';else cout<<'b'; }else{ if(s[i]!='z')cout<<'z';else cout<<'y'; } } cout<<endl; } }
B
1、描述
一个英雄有A的攻击力的B的血量,n个怪物有攻击力a[i]和血量b[i],每次战斗会消耗对方攻击力的血量,英雄与怪物i都扣血。问,英雄有没有可能消灭所有怪物,包括自己被最后一只打倒的情况。
2、分析
对每只怪物,计算它需要打几次,从而得到消灭它需要消耗多少血量,因为打怪顺序不影响消耗。再找出攻击最高的怪物攻击力是多少,把它留到最后。sumlost-maxattack,就是答案。
3、代码
#include<iostream> #include<stdio.h> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; #define int long long #define _for(i,a,b) for(int i=(a);i<=(b);i++) #define _rp(i,b,a) for(int i=(b);i>=(a);i--) #define mst(f,a) memset(f,a,sizeof(f)) #define close std::ios::sync_with_stdio(0) typedef long long ll; const int N=2e5+7; int n,t,at,hp,a[N],b[N],ma,lost; string s; signed main(){ cin>>t; while(t--){ cin>>at>>hp>>n; ma=0;lost=0; _for(i,1,n){ cin>>a[i]; ma=max(ma,a[i]); } _for(i,1,n)cin>>b[i]; _for(i,1,n){ lost+=a[i]*(((b[i]-1)/at)+1); } if(lost-ma<hp)cout<<"YES\n";else cout<<"NO\n"; } }
C
1、描述
oj手里藏着一条长度为n的an序列,并且序列元素1~n只出现一次。程序每次输出?+数字i,询问a[i]是多少。在不超过100次的询问里找到一个位置p,使得a[p-1]>a[p]<a[p+1],输出! P,结束程序。n<=1e5
2、分析
一开始想到的是微积分的某个中值定理,当区间两边相等时,区间内必定有至少一个点,满足它的值小于左右两点的值。然后本题也是在找一个峰谷,再加上2^30>1e5,就可以尝试用二分来找。那二分如何判断l与r谁转到m上呢?怎么找峰谷呢?
一开始,我先询问1、2、n-1、n的点值,如果找到就直接输出结束程序,否则两端就会形成一个向中间倾斜的坡。那么对于位置m,读取m-1,m,m+1,判断m点是向左倾斜还是向右倾斜,向左倾斜则区间向左缩小,否则向右缩小,因为如果一个区间两端都是向中间倾斜的,那么中间一定有一个转折点,转折点就是局部最小点。
然后不知道,哪里卡了,看了一些别人的代码,发现不需要特别读入1、2、n、n-1,因为0和n+1是一个极大值,那么两端必然有一个向中间倾斜的斜坡。其次,只读入m和m+1,判断m与m+1是向哪里倾斜,因为an数组中不可能出现相等的值,所以必然是倾斜的,有倾斜,就可以找到转折点。
换句话说,倾斜就相当于导数不等于0,向下倾斜导数小于0,向上倾斜导数大于0,那么在倾斜交界的地方就是导数等于0,也就是极值点,转折点。
3、代码
#include<iostream> #include<stdio.h> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; //#define int long long #define _for(i,a,b) for(int i=(a);i<=(b);i++) #define _rp(i,b,a) for(int i=(b);i>=(a);i--) #define mst(f,a) memset(f,a,sizeof(f)) #define close std::ios::sync_with_stdio(0) typedef long long ll; const int N=2e5+7; int n,t,a[N],l,r,m; string s; void que(int x){ if(a[x]||x>t)return; cout<<"? "<<x<<endl; cout.flush(); cin>>a[x]; } signed main(){ cin>>t; n=t; mst(a,0); a[0]=a[n+1]=N; l=1;r=t; while(l<r){ m=(l+r)>>1; que(m);que(m+1); if(a[m]<a[m+1]){ r=m; }else{ l=m+1; } } cout<<"! "<<l<<endl; }
这也是我第一次写交互题,感悟比较新鲜。然后我对c++的理解也比较粗浅,语言相对朴素点。
交互题是与其他题目最大的区别是,普通题先输入,后输出,而交互要一遍遍先输出(询问)后输入(读取),直到找到答案后,输出答案,结束程序。
这种特殊之处,体现在题面给出一种输出方法,但由于比赛时间紧,俺也没法认真推敲,只能事后补了。
- fflush(stdout) or cout.flush() in C++;
- System.out.flush() in Java;
- flush(output) in Pascal;
- stdout.flush() in Python;
- see documentation for other languages.
根据网上博客的说法,大概是程序与oj之间,如果程序直接输出(询问),oj将无法接受,因此不告诉程序信息,导致程序接下来读不到东西,最后re。通过在原本的输出下面加一句cout.flush(),也就是上述的函数,就可以让程序与oj之间愉快对话。而coder接下来要思考的,就是算法该如何写了。
ps:最近转五笔输入,打字非常慢,所以尽量精简一些。