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:最近转五笔输入,打字非常慢,所以尽量精简一些。

 

posted @ 2022-03-08 21:23  windiest  阅读(419)  评论(0编辑  收藏  举报