競技プログラム練習会

2020 Normal

第十七回

後期第五回

担当:zeke

自己紹介

自己紹介

  • KMCid:zeke(本名:岡島和希)
  • 競プロではC++を使ってるよ
  • AtCoder水
  • 青になりたい
  • 最近競プロがおざなりに…
  • ハル研プロコンに出ました
  • 無事にMT免許の卒検受かりました
  • 来年対面新歓復活するんですかね?
  • #zeke-memo

私用で3回ほど練習会が休止してしまい

申し訳ない…

 

今週からは

  • 毎週一人ずつ交代で講座をやります
  • おそらく一人一回ぐらいできそう?(3~5人参加予定)

講座やるよ!

  • テーマ決め
  • スライドづくり
  • コンテスト作り
  • 内部wiki更新
  • 告知

担当になったらやること

(去年のスライド抜粋)

  • ぶっちゃけここが一番難しいかも
  • 競プロに関するものなら何でもいいです
    • 蟻本の任意の章の内容
    • 任意のコンテストの復習
    • 前期にやったやつでよくわかっていなかったのを復習
  • 昨年の例
    • セグメント木、ダブリング...
    • DP,二分探索...

テーマ決め

  • 一番面倒
  • どんな形式でもおk(PowerPoint,SlideShare,slides.com)
  • これはslides.com.簡単にシェアできる&タダだが、URLさえわかれば内容はダダモレです
  • 結構大変かもしれないので分からないことがあれば遠慮なく聞いてください
  • 去年のスライドも参考にしてみてください
  • #2020-procon、#proconでも聞いてみよう!

スライドづくり

  • コンテストを立てます
  • コンテストの種類
    • Virtual Judge
    • AOJ
    • AtCoderばちゃ
    • AtCoder Problemsばちゃ
  • 上二つを使ったことがあります
  • テーマに関する例題を探して出題するといいです
  • AtCoderTagsとかで探すといいです
  • 結構問題設定とかに癖があるので分からなかったら聞いてください

コンテスト作り

  • wiki更新したことありますか?
  • 編集ボタン押して雑にテンプレートをコピーして使えばおkです
  • スライド、コンテストURLなどを載せておくといいです
  • もし、PowerPointとかで作ったファイルをKMCのサーバーにアップロードしたい場合言ってください(scpの使い方等教えます)

内部wiki更新

  • 始まる15分前くらいに #2020-procon のほうで@channel飛ばして告知をしましょう!
  • ZoomかDiscordで発表しましょう!

告知&発表

順番決めましょう

  • 12/4 
  • 12/11 Flkanjin
  • 12/18 replica
  • 1/8 taku_sea
  • 1/15 laft

辺りの日程でできそう?(たぶん)

1/15はセンター前日だったりする

初回が私が

のんびりICPCを

振り返ってみようと

思います(遅い)

ICPC2020-A

はいやるだけ

int main() {
    while(1){
        int n;
        cin>>n;
        if(n==0){
            break;
        }
        V vec(n);
        rep(i,n)cin>>vec[i];
        ll cnt=0;
        rep(i,n-3){
            if(vec[i]==2&&vec[i+1]==0&&vec[i+2]==2&&vec[i+3]==0){
                cnt++;
            }
        }
        cout<<cnt<<endl;
    }
    
}

ICPC2020-B

問題文長い…

まあ

  • 濃厚接触したら感染するから、どこまで感染が広がるかをこたえるだけ
  • UnionFindかなと思ったけど、普通に配列でフラグを立てていくだけでなんとかなりそう
int main() {
    while(1){
        ll m,n,p;
        cin>>m>>n>>p;
        p--;
        if(!m){
            break;
        }
        V vec(m);
        vec[p]++;
        rep(i,n){
            ll x,y;
            cin>>x>>y;
            x--;y--;
            if(vec[x]|vec[y]){
                vec[x]=1;
                vec[y]=1;
            }
        }
        ll res=0;
        rep(i,m){
            res+=vec[i];
        }
        cout<<res<<endl;
    }
}

ICPC2020-C

ここら辺からムズイ

まずは愚直に

  • 一番愚直にやるとO(p^3)かかり無理
  • wを決め打ちしてO(√p)そこからdとhをきめるO(√p)をしてO(p)
  • さらに最初に約数列挙してあげることでO(√p)かO((約数の数)^2)まで落とせる(本番時はこれで通した)(2分かかる)
  • w<d<hを意識するともっと早くなるかも?
int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(10);
    ll index=0;
    while (1) {
        cerr<<index<<endl;
        index++;
        ll n;
        ll res = INF;
        cin >> n;
        if (n == 0) {
            break;
        }
        ll n_temp = n;
        vector<ll> yakusuu;
        yakusuu.push_back(1);
        ll Wmax=sqrt(n);
        rep3(i, 2,Wmax+1000) {
            if (n % i == 0) {
                yakusuu.push_back(i);
            }
        }
        ll limit=pow(n,1/3)+100;
        cerr<<yakusuu.size()<<endl;
        for (auto i : yakusuu) {
            ll n_2 = n / i;
            for(auto j:yakusuu){
                if(n_2%j==0){
                    chmin(res,i+j+(n_2/j));
                }
            }
        }
        cout << res << endl;
    }
}

ICPC2020-D

通せなかった…

ICPC2020-D

  • 見たところ構文解析?
  • でもなんかO(N!)が湧くけど間に合わない
  • 逆ポーランド記法の木 みたいに考えて枝刈り?
  • 片方で決め打ち?
  • どうもうまくいかない…

終わった後Twitterで

  • 答えの記号を決め打ちした後は、その前に来るか後に来るかだけを気にしてればいいんじゃない?
  • 天才か??????

解法

  • まずは記号→数字の写像を作る(例:C→0:I→1:P→2)
  • 何番の記号が答えに来るかを先に決めておく(N)
  • 仮にi番目が来るとするならばi-1個の記号をi番目の記号より前に来てn-i個の記号が後に来ると考えておく
  • あとはS1とS2にこれを当てはめてうまくいくかを確かめるだけO(N!/(N/2)!)
  • O(N N!/(N/2)!)となり余裕で間に合う!

詳細

  • i番目より前に来る記号に対して-1をi番目の記号に0を後に来る数字に1をセットしておくと便利
  • next_permutation(順列並び替え)が使えると実装が楽に
  • >はmax(x,y)、<はmin(x,y)と考える

 

 

例1:PCIの順番つまりC=0,I=1,P=-1の時、

max(min(1,0),min(-1,0))=0

max(-1,0)=0

となり両方0になるのでうまくいくパターンだと分かる

// C++ は素で書く?
/*
    Author:zeke

    pass System Test!
    GET AC!!
*/
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
using ll = long long;
using ld = long double;
using namespace std;
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
#define all(x) (x).begin(), (x).end()
#define rep3(var, min, max) for (ll(var) = (min); (var) < (max); ++(var))
#define repi3(var, min, max) for (ll(var) = (max)-1; (var) + 1 > (min); --(var))
#define Mp(a, b) make_pair((a), (b))
#define F first
#define S second
#define Icin(s) \
    ll(s);      \
    cin >> (s);
#define Scin(s) \
    ll(s);      \
    cin >> (s);
template <class T>
bool chmax(T& a, const T& b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <class T>
bool chmin(T& a, const T& b) {
    if (b < a) {
        a = b;
        return 1;
    }
    return 0;
}
typedef pair<ll, ll> P;
typedef vector<ll> V;
typedef vector<V> VV;
typedef vector<P> VP;
ll mod = 1e9 + 7;
ll MOD = 1e9 + 7;
ll INF = 1e18;
#define int ll
// cout << "Case #" << index << " :IMPOSSIBLE";
int modPow(long long a, long long n, long long p) {
    if (n == 0) return 1;  // 0乗にも対応する場合
    if (n == 1) return a % p;
    if (n % 2 == 1) return (a * modPow(a, n - 1, p)) % p;
    long long t = modPow(a, n / 2, p);
    return (t * t) % p;
}
typedef string::const_iterator State;
// powMod(a,b,MOD)
// O(log(b))?
int expression(State& begin);
map<char, ll> mp;
V permu;
void mp_init() {
    map<char, ll> temp;
    mp = temp;
}
int number(State& begin) {
    int res = permu[mp[*begin]];
    return res;
}
int factor(State& begin) {
    begin++;
    int ret = expression(begin);
    begin++;
    return ret;
}
int expression(State& begin) {
    // cout<<"init "<<*begin<<endl;
    int ret = 0;
    if (*begin - 'A' < 26 && *begin - 'A' >= 0) {
        ret = number(begin);
        begin++;
    }

    if (*begin == '(') {
        ret = factor(begin);
    }
    if (*begin == '>') {
        begin++;
        return max(ret, expression(begin));
    }
    if (*begin == '<') {
        begin++;
        return min(ret, expression(begin));
    }
    return ret;
}
int kaijo(int k) {
    ll res = 1;
    rep3(i, 1, k + 1) { res *= i; }
    return res;
}
signed main() {
    while (1) {
        ll n;
        cin >> n;
        if (!n) {
            break;
        }
        mp_init();
        string s;
        cin >> s;
        for (auto i : s) {
            mp[i] = mp.size();
        }
        ll res = 0;
        string s1, s2;
        cin >> s1 >> s2;
        rep(i, n) {
            permu = {};
            rep(j, n) {
                if (j < i) {
                    permu.push_back(-1);
                } else if (i == j) {
                    permu.push_back(0);
                } else {
                    permu.push_back(1);
                }
            }
            do {
                State x1 = s1.begin();
                State x2 = s2.begin();
                if (!(expression(x1)) && !(expression(x2))) {
                    for(auto k:permu){
                        cout<<k<<" ";
                    }
                    cout<<endl;
                    res += kaijo(i) * kaijo(n - i - 1);
                }
            } while (next_permutation(all(permu)));
        }
        cout << res << endl;
    }
}

構文解析について

  • 実装ゲーなところがある
  • 自分はよく文字リテラルじゃなくてイテレータで処理しています(おそらく使いやすい)
  • 構文解析はこちらが分かりやすいです
  • 普通のカッコつき四則演算の式を計算するコードを一度書いてみると練習になるかも?

ICPC2020-E~

私の実力ではまだ

ACできません…

尺が余ってしまった…

構文解析

やるだけなのですが…

  • 慣れないと難しいので、四則演算(+,-,*,/,(,),num)を実装してみよう
  • 正規表現の一種
  • プログラミング言語の文法定義に使われる
  • 以下のような文法規則がある(wikiより)
  • 省略可能なアイテムは角括弧で囲む。例えば、[<item-x>]
  • 0回以上繰り返すアイテムは中括弧で囲む。例えば、<word> ::= <letter> { <letter> }
  • 1回以上繰り返すアイテムには '+' を後置する。<word> ::= <letter>+
  • 終端記号をボールド体、非終端記号を通常の文字で表し、イタリック体や山括弧を使わない。
  • アイテムの繰り返しを '*' を後置することで表すことが多い。
  • 生成時の選択肢を '|' 記号で区切って列挙する。例えば、<alternative-a> | <alternative-b>
  • アイテムをグループ化する必要があるときは、普通の括弧で囲む。
  • セミコロンに続けて註釈を付ける[6]

百聞は一見に如かず

expr :=<term> | <expr> + <term> | <expr> - <term>

term :=<factor> | <term> * <factor> | <term> / <factor>

factor:=(<expr>) | <number>

number:=[0-9]*

全ての数式はexprの形をとると考える

例:(1+3*5)+(2+(1*5+8)+10)*2

<expr>

<expr>+<term>

<term>+<term>*<factor>

<factor>+<factor>*<number>

(<expr>)+(<expr>)*2

(<expr>+<term>)+(<expr>+<term>)*2

(<term>+<term>*<factor>)+(<expr>+<term>+<factor>)*2

(<factor>+<factor>*<number>)+(<term>+<factor>+<number>)*2

(<number>+<number>*5)+(<factor>+(<expr>)+10)*2

(1+3*5)+(<number>+(<expr>+<term>)+10)*2

(1+3*5)+(<number>+(<term>*<factor>+<number>)+10)*2

(1+3*5)+(2+(<factor>*<number>+8)+10)*2

(1+3*5)+(2+(<number>*5+8)+10)*2

(1+3*5)+(2+(1*5+8)+10)*2

大変だった…

  • しかし、BNFで実装しようとすると不都合が出てくる(exprを評価する際にtermなのかexprなのかわからない問題)
  • そこで改良されたEBNF規則を使っていく

expr:=<term>[+,-<term>]*

term:=<factor>[*,/<factor>]*

factor:=(expr) | number

number:=[0-9]*

階層になっていることが分かる

実装

  • expr,term,factor,numberをそれぞれ関数にして必要に応じて読んでいく形にする
  • 今どこを見ているかは文字イテレータを使って処理する
  • どこで1文字進めるかのところでミスりやすいので注意!
typedef string::const_iterator State;
int number(State &begin);
int term(State &begin);
int factor(State &begin);
int expr(State &begin);

int number(State &begin){
    int ret=0;
    while(isdigit(*begin)){
        ret*=10;
        ret+=(*begin-'0');
        begin++;
    }
    return ret;
}
int term(State &begin){
    int ret=factor(begin);
    while(1){
        if(*begin=='*'|*begin=='/'){
            if(*begin=='*'){
                begin++;
                ret*=factor(begin);
            }else{
                begin++;
                ret/=factor(begin);
            }
        }else{
            break;
        }
    }
    return ret;
}
int factor(State &begin){
    if(*begin=='('){
        begin++;
        int ret=expr(begin);
        begin++;
        return ret;
    }else{
        return number(begin);
    }
}
int expr(State &begin){
    int ret=term(begin);
    while(1){
        if(*begin=='+'|*begin=='-'){
            if(*begin=='+'){
                begin++;
                ret+=term(begin);
            }else{
                begin++;
                ret-=term(begin);
            }
        }else{
            break;
        }
    }
    return ret;
}


int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(10);
    string s;
    while(cin>>s){
        State x=s.begin();
        cout<<expr(x)<<endl;
    }
}

コンテスト

  • ICPC2020-Dと構文解析の問題を混ぜています
  • A,Bは確かICPCの過去問
  • 難しいかもしれませんがチャレンジしてみてください
  • パスワード:y

競技プログラム練習会2020 Normal 後期第五回

By kmc_procon2020

競技プログラム練習会2020 Normal 後期第五回

  • 68