みちらからだよー

競プロとかその他しょうもない事とか

入水したのでうれしいなと言う

入水しました

中三水コーダーのみちらからです。前回のARC162で入水したので記事を書きます。

うれしいな

うれしいです。AtCoder始めたときに知ってた強い人(今はやっていない人)が水コーダーだったので驚いています。水コーダーは上級者のボーダーだと感じていたので感慨深いですね。(ちなみに黄コーダーは人外ボーダーです)

やったこと

実は緑コーダーの間に解いた問題数は少ないのですが、高めのdiffの問題を多く解いていたので緑、茶diffは大体一瞬で解法が浮かぶようになりました。精進って大事ですね。

あとC++に移行しました。きっかけはJOI本選です。入緑まではPythonを使ってAtCoderの問題を解いていたのですが、JOI本選ではC++しか使えなかったのでかなり結果はボロボロでした。それが悔しかったので次のABCから急にC++に使用言語を変えました(は?)。今はC問題まではPython、D以降はC++で解くスタイルになっています。C++魔改造テンプレートやマイナーデータ構造ライブラリを作ったことにより、Pythonより速いスピードでコーディングができるようになりました。例をお見せしましょう

int main(){
    INT(n,k,q);
    vl a(n,0);
    Treap<> ps;
    rep(i,n){
        ps.insert(0);
    }
    while(q--){
        INT(x,y);x--;
        ps.erase(a[x]);
        ps.insert(y);
        a[x]=y;
        print(ps.query(n-k,n));
    }
}

これはABC306のE問題です。ライブラリ、テンプレートは省略しています。 とても短いですね。どれくらい短いのかというとC++の解説コードは65行もありますが、このコードは15行しかありません。つまり1/4以下の記述量で問題が解けるということです。うれしいね。

いろいろなデータを見る

パフォーマンスのグラフ


パフォーマンスのグラフです。相変わらずパフォーマンスが不安定ですね。
一度灰perfが出ていますね。本当に水コーダーか?
割と水perfが安定してきているような気がしないでもないのでこの調子で頑張りたいです。

Heatmapのmax difficulty


ちょうど真ん中までが茶コーダー時代、それ以降が緑コーダー時代です。
明らかに水や青の量が多いですね!えらい!

精進グラフ


赤コーダーです!やったね!!(うれしくない)

コードゴルフ楽しいよという話

コードゴルフに少しハマってshortestを30個くらい持っています。 Pythonメインでやっているのですが、Pythonの謎記法やCythonのfor文の仕様を知ることができたりしてABC前半の早解きにもかなり役立っている気がします。最初のshortestから100byte近く更新できることもあってすごく楽しいです。ぜひコードゴルフをやってみてください。でも私のshortestは更新しないでくださいお願いします何でもしますから

目標

今年中に青コーダーになりたいです。
これまでの色変は全て半年おきに達成しているので青にも半年でなりたいですね。
それでは12月、入青記事でお会いしましょう。ではまた!

Mo'sの計算量を考える

まえがき

前回のABC293-GでMo's Algorithmを学んだのですが、計算量が直感的にわかりにくいと思ったので備忘録として書きます。

そもそもMo'sってなに

概要

  • 範囲Nの中の [l,r) 形式のクエリがQ個与えらる問題
  • [l,r)の結果から一個だけずらして[l+1,r)とか[l,r+1)とかにする操作が軽い(O(log N)くらいまで)
  • クエリがオフラインで与えられる
    問題を、ずらす操作の計算量をO(t)としたときにO(tN\sqrt Q)で解くことのできるアルゴリズム

やること

  1. \lfloor N/\sqrt Q\rfloorWとする
  2. 次の法則でソートする
    • \lfloor l/W\rfloorが小さい順にソートする
    • \lfloor l/W\rfloorが同じクエリはrでソートする(\lfloor l/W\rfloorの偶奇によって昇順、降順を変えると定数倍が速くなる)
  3. あとは最初のクエリから順番に尺取り法のようなことをして処理していく

コード

ほとんどei1333さんの実装を写経しました
ごめんなさい

  • Mo mo(n) で範囲がnの構造体を作る
  • mo.add(int l,int r)[l,r)のクエリを追加する
  • mo.build(add,erase,out) クエリの幅を一つ伸ばすときの関数add、縮めるときの関数erase、クエリに保存する関数outを与えたら全部やってくれる
  • mo.build(add_left,add_right,erase_left,erase_right) 左右に伸ばすときや縮めるときの関数が違うときにどうぞ
struct Mo{
    int n;
    vector<pair<int,int>> lr;
    explicit Mo(int n):n(n){}

    void add(int l,int r){/*[l,r)*/
        lr.emplace_back(l,r);
    }

    template<typename AL,typename AR,typename EL,typename ER,typename O>
    void build(const AL &add_left,const AR &add_right,const EL &erase_left,const ER &erase_right,const O &out){
        int q=lr.size();
        int width=n/min<int>(n,sqrt(q));
        std::vector<int> order(q);
        std::iota(order.begin(),order.end(),0);
        std::sort(order.begin(),order.end(),[&](int a,int b){
            int a_block=lr[a].first/width,b_block=lr[b].first/width;
            if(a_block!=b_block)return a_block<b_block;
            return a_block%2==0 ? lr[a].second<lr[b].second : lr[a].second>lr[b].second;
        });

        int l=0,r=0;
        for(auto i:order){
            while(l>lr[i].first)add_left(--l);
            while(r<lr[i].second)add_right(r++);
            while(l<lr[i].first)erase_left(l++);
            while(r>lr[i].second)erase_right(--r);
            out(i);
        }
    }
    template<typename A,typename E,typename O>
    void build(const A &add,const E & erase,const O &out){
        build(add,add,erase,erase,out);
    }
};

本題

合計でQ個のクエリがあり、クエリがソートされた時のlは毎回高々\frac{N}{\sqrt Q}しか動かない(幅\frac{N}{\sqrt Q}ごとにソートされているので)。
なのでlを動かすのは合計でQ\times \frac{N}{\sqrt Q}回。
分母と分子に\sqrt Qを掛けると\frac{QN\sqrt Q}{Q}になるので結局ずらす操作をO(t)としたときにO(tN\sqrt Q)になる。


次はrについて考える。
各ブロックごとに高々N回しか移動しない。
ブロックは\sqrt Q個あるので、ずらす操作をO(t)としたときに合計O(tN\sqrt Q)になる。

すげー(小並感)

あとがき

なぜ本題の方が短いのですか
ありがとうございました。

JOI本選 参戦記

まえがき

こんにちは、みちらからです。先週末に行われたJOI本選に参加したので参戦記を書いておきます。(緑コーダー 199点なのであまり参考にはならないと思います)

問題1

問題概要

色がたくさんある片方からしか置けない一次元オセロをやるから結果を出力してね

感想

これは結構簡単でした。方針は一瞬で思いついたので気合で実装しました。C++でmapやsetなどを使ったことがなかったので少し時間がかかってしまいましたが満点を取ることができたのでうれしかったです。

コード
int main(){
    int n;
    cin>>n;
    vi a(n);
    vin(a);
    map<int,set<int>> m;
    rep(i,n){
        m[a[i]].insert(i);
    }
    vi toout(n,0);
    int i=0;
    while(i<n){
        auto it=m[a[i]].lower_bound(i);
        if(++it==m[a[i]].end()||*it==i+1){
            toout[i]=a[i];
            i++;
            continue;
        }
        reps(j,i,*it+1){
            toout[j]=a[i];
        }
        i=*it;
    }
    rep(i,n)cout<<toout[i]<<endl;
}

問題2

問題概要

影響力が大きい人に本をあげるほど周りのひとが本を買うよ
全員が本を持つようにするために必要な本をあげる人をできるだけ少なくしてね

感想

さっそく結構難しかったです。
O(N2)貪欲で69点が取れました。むずくね!?

コード

本番59点コード

int main(){
    int n;
    cin>>n;
    set<pair<int,int>> b;//eとx逆
    rep(i,n){pair<int,int> tmp;cin>>tmp.second>>tmp.first;b.insert(tmp);}

    //ここでO(N^(N/2))?
    int cnt=0;
    while(b.size()>0){
        cnt++;
        pair<int,int> ex;
        ex=*b.rbegin();
        vector<pair<int,int>> er;
        for(pair<int,int>i:b){
            if((ex.first-i.first)>=abs(ex.second-i.second))er.push_back(i);
        }
        for(pair<int,int>i:er){
            b.erase(i);
        }
    }
    cout<<cnt<<endl;
}

+10点コード

int main(){
    int n;
    cin>>n;
    set<pair<int,int>> a;
    rep(i,n){
        int x,y;
        cin>>x>>y;
        a.insert(make_pair(x,y));
    }
    cout<<sz(a)<<endl;
}
おまけ

upsolveした満点コード

int main(){
    int n;
    cin>>n;
    vector<pair<int,int>> a(n);
    rep(i,n){
        pair<int,int> tmp;
        cin>>tmp.first>>tmp.second;
        a[i]=tmp;
    }
    sort(all(a));
    vector<pair<int,int>> stack;
    stack.push_back(a[0]);
    reps(i,1,n){
        int x,e,nx,ne;
        x=stack[stack.size()-1].first,e=stack[stack.size()-1].second;
        nx=a[i].first,ne=a[i].second;
        while(ne-e>=abs(nx-x)&& stack.size()>0){
            stack.pop_back();
            x=stack[stack.size()-1].first,e=stack[stack.size()-1].second;
            nx=a[i].first,ne=a[i].second;
        }
        if(stack[stack.size()-1].second-a[i].second<abs(stack[stack.size()-1].first-a[i].first))stack.push_back(a[i]);
    }
    cout<<stack.size()<<endl;
}

問題3

問題概要

Stronger Takahashiの壊すブロックの大きさが決められてないよ
がんばってね

感想

Stronger Takahashiをちょっとだけ変えて27点
解説見て思ったこと
- このへんからJOI本選のとてつもない実装量感が出てきた

コード

本番27点コード
けんちょんさんのブログからほぼコピペです
ここ

// 四方向への移動
const vector<int> dx = {1, 0, -1, 0};
const vector<int> dy = {0, 1, 0, -1};

// 無限大
const int INF = 1<<29;

// マスを表す構造体
using pint = pair<int,int>;

int main() {
    // 入力
    int H, W,N,sx,sy,gx,gy;
    cin >> H >> W>>N>>sx>>sy>>gx>>gy;
    sx--;sy--;gx--;gy--;
    vector<string> fi(H);
    for (int i = 0; i < H; ++i) cin >> fi[i];

    // deque
    deque<pint> que;
    vector<vector<int>> dp(H, vector<int>(W, INF));
    que.push_back({sx, sy});
    dp[sx][sy] = 0;

    // 0-1 BFS
    while (!que.empty()) {
        // que の先頭要素を取り出す
        auto [x, y] = que.front();
        que.pop_front();

        // 通路マスへの移動
        for (int dir = 0; dir < 4; ++dir) {
            int nx = x + dx[dir], ny = y + dy[dir];
            if (nx < 0 || nx >= H || ny < 0 || ny >= W) continue;
            if (fi[nx][ny] == '#') continue;

            // 移動コストは 0 なので que の front に push
            if (dp[nx][ny] > dp[x][y]) {
                dp[nx][ny] = dp[x][y];
                que.push_front({nx, ny});
            }
        }

        // 魔法を唱えて移動
        for (int nx = x - N; nx <= x + N; ++nx) {
            for (int ny = y - N; ny <= y + N; ++ny) {
                if (abs(nx - x) + abs(ny - y) == N*2) continue;
                if (nx < 0 || nx >= H || ny < 0 || ny >= W) continue;

                // 壁だろうが通路だろうがコスト 1 の辺を張る
                // コスト 1 なので que の back に push
                if (dp[nx][ny] > dp[x][y] + 1) {
                    dp[nx][ny] = dp[x][y] + 1;
                    que.push_back({nx, ny});
                }
            }
        }
    }
    cout << dp[gx][gy] << endl;
}

問題4

時間足りねえ!!無理!!!

問題5

シンプルに無理!!!

まとめ

集中力のある序盤のうちに考察などをしておいた方がよかったかもしれない。
来年のこの時期は青コーダーになっている予定なので来年は春合宿行きます
あとJOI始まる前によく寝ておいた方がよいです。
途中で眠くなって集中が切れかけました。
今回は春合宿メンバーには入れませんでしたが、すごく楽しかったので来年も楽しんで頑張りたいです!

C++のテンプレートを作った話

//include std
#include <iostream> // cout, endl, cin
#include <string> // string, to_string, stoi
#include <vector> // vector
#include <algorithm> // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <utility> // pair, make_pair
#include <tuple> // tuple, make_tuple
#include <cstdint> // int64_t, int*_t
#include <cstdio> // printf
#include <map> // map
#include <queue> // queue, priority_queue
#include <set> // set
#include <stack> // stack
#include <deque> // deque
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <bitset> // bitset
#include <cctype> // isupper, islower, isdigit, toupper, tolower

using namespace std;

ここはincludeを大体全部のっけてます。Clangでやるときのために#include <bits/stdc++.h>はやっていません。

//コンパイラと浮動小数点高速化
//#pragma GCC optimize("Ofast")//危険かも
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
//cin,endlの高速化
//インタラクティブのときは外す
struct Fast {Fast() {std::cin.tie(0); ios::sync_with_stdio(false);}} fast;
#define endl '\n'

ここはどういう意味かよくわかっていません。とりあえずendlは遅いので'\n'に置き換えているということはわかります。

//using
using ll=long long;
using vi=vector<int>;
using vl=vector<ll>;
using vvi=vector<vi>;
using vvl=vector<vl>;

vi、vviがめちゃくちゃ便利です。これもっと前に知りたかった... vector<vector> a(n,vector(n))vvi a(n,vi(n))と書くことができます。

//define
#define itn int//これすき
 
/*最終兵器
絶対使うな
#define int long long
 */

説明不要ですね。上の#define itn intがめちゃくちゃ好きです。確か強い人のテンプレートにありました。

//マクロ
#define reps(i, a, n) for (ll i = (a); i < (ll)(n); ++i)
#define rep(i, n) reps(i, 0, n)
#define rrep(i, n) reps(i, 1, n + 1)
#define per(i,n) for(ll i=n-1;i>=0;i--)
#define pper(i,n) for(ll i=n;i>=1;i--)
#define all(x) (x).begin(),(x).end()
#define perm(c) sort(all(c));for(bool c##p=1;c##p;c##p=next_permutation(all(c)))
#define sz(x) ((int)(x).size())
#define YesNo(bool) if(bool){cout<<"Yes"<<endl;}else{cout<<"No"<<endl;}
#define vout(vi) rep(i,sz(vi))cout<<vi.at(i)<<" \n"[i+1==n];

マクロです。pperとかあまり有効活用できる気がしませんがまあよいでしょう。あとvoutはデバッグに便利そうです。

全体のコード

//include std
#include <iostream> // cout, endl, cin
#include <string> // string, to_string, stoi
#include <vector> // vector
#include <algorithm> // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <utility> // pair, make_pair
#include <tuple> // tuple, make_tuple
#include <cstdint> // int64_t, int*_t
#include <cstdio> // printf
#include <map> // map
#include <queue> // queue, priority_queue
#include <set> // set
#include <stack> // stack
#include <deque> // deque
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <bitset> // bitset
#include <cctype> // isupper, islower, isdigit, toupper, tolower
 
using namespace std;
//コンパイラと浮動小数点高速化
//#pragma GCC optimize("Ofast")//危険かも
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
//cin,endlの高速化
//インタラクティブのときは外す
struct Fast {Fast() {std::cin.tie(0); ios::sync_with_stdio(false);}} fast;
#define endl '\n'
//using
using ll=long long;
using vi=vector<int>;
using vl=vector<ll>;
using vvi=vector<vi>;
using vvl=vector<vl>;
//define
#define itn int//これすき
 
/*最終兵器
絶対使うな
#define int long long
 */
 
//マクロ
#define reps(i, a, n) for (ll i = (a); i < (ll)(n); ++i)
#define rep(i, n) reps(i, 0, n)
#define rrep(i, n) reps(i, 1, n + 1)
#define per(i,n) for(ll i=n-1;i>=0;i--)
#define pper(i,n) for(ll i=n;i>=1;i--)
#define all(x) (x).begin(),(x).end()
#define perm(c) sort(all(c));for(bool c##p=1;c##p;c##p=next_permutation(all(c)))
#define sz(x) ((int)(x).size())
#define YesNo(bool) if(bool){cout<<"Yes"<<endl;}else{cout<<"No"<<endl;}
#define vout(vi) rep(i,sz(vi))cout<<vi.at(i)<<" \n"[i+1==n];
#define vin(vi) rep(i,sz(vi))cin>>vi.at(i);

入緑記事です

この記事は、競プロ Advent Calendar 2022 - Adventar 20日目の記事です。

 

こんにちは、競技プログラミングを初めてちょうど一年と一日たった、みちらからです(AtCoder、TwitterID: michirakara)。11/26のABC279で入緑したのでポエム&やったことなどを書いていこうと思います。

灰色時代

AtCoderを始めたのは2021年の終わりくらいからです。当時はTwitterではスピードキューブ界隈に属していました。TLでAtCoderをやっている人を複数人見かけていたのでとりあえずやってみようと思い始めたはずです。(記憶があいまいなので違うかも...)ちなみに初めて出場したコンテストは12/25のABC233らしいです。クリスマスに何やってんだ俺は...

半年くらいで入茶したのですがProgress Chartを見る限りあまり精進はしていないっぽいです。

入茶したときはARCで嘘解法で2完して爆上げしてました。<-これ今考えてもおかしい

確か使えるようになったアルゴリズム

  • 累積和
  • bit全探索
  • RLE

くらいだったと思います。ちなみにRLEは自分で思いつきました。すごくない?褒めて!!!

茶色時代

これめちゃくちゃ長く感じました。ほんとうにつらかった。最初の一か月はずっと単調増加でめちゃくちゃ調子がよかったのですが、そのあと伸び悩み期が来て3か月ぐらいレートを冷やしていました。実は一番精進していたのはその伸び悩み期の間だったので、精進の成果は遅れてくるものなのかなと思ったりしています。そのあとは750くらいまでサクッといきましたが、そこからは毎回+10くらいでゆっくり伸びていきました。ちなみに入緑したABCではちょうどレート800になりました。問題は結構たくさん解いていますね。あと盆栽の魅力にも気が付きました。ライブラリをペタするだけの問題とかを秒で解くことができるので楽しいです。まだまだライブラリの数は少ないのでこれからもっと増やしていきたいですね。


使えるようになったアルゴリズムやデータ構造など

  • 木、グラフ 
    • Union-Find
    • DFS
    • BFS
    • Dijkstra
    • Segment Tree
  • 累積和
    • 一次元累積和
    • 二次元累積和
    • imos法
  • 探索
    • 二分探索
    • しゃくとり法
  • 数学
  • DP
    • 基本的な配るDP
    • 基本的なもらうDP 
    • 基本的な期待値DP

こう見るとめちゃくちゃいろいろなアルゴリズムを使えるようになってますね...これからは桁DPとかbitDPとかワーシャルフロイドとかも勉強していきたいです。

色々見る

AtCoder ProblemsのAchievementです。1050ACしています。緑にしてはめちゃくちゃ多い方だとは思います。才能とか無いので努力で頑張りました。

精進グラフです。黄色コーダーじゃん。わーい!!(はあ...)

AtCoder Piesです。まあまあ茶色とか緑も埋まってきました。ちなみに黄色diff通してるのはnanと出力するだけでACになる問題です。水diffはまだまだ難しくてあまり解けていません。

AtCoder Chartsです。パフォ安定しないマンだねえ...

おわり

来年のアドカレの入青記事でお会いしましょう。それでは!

HACK TO THE FUTURE 2023 予選 参戦記(AHC016)

※これは日記です 読みにくいかもしれません

一日目

起床コンテスト

無事起きることに成功しました。時差の影響で真夜中の2時に起きなくてはいけないので早く寝ました。えらいです。始まるまではゆっくりボカロの曲でも聞いていましょう。

一応テンプレ的なものも作ってきました。今回はsublime textでプログラムを書いていこうと思います。VSCodeでプログラムを実行してもPCのスペックが悪すぎてまともに実行時間を計れないのでプログラムを実行したりするのはWandboxになると思います。

コンテスト開始

何だこのややこしい問題...グラフとか難しそうです...

初提出はWAでした...20分くらいでバグを見つけて修正したので提出まで10分間休憩です。

正の得点を取りました。

暫定105位です。

方針:N=100、i個目のグラフはi個目の頂点に全部辺をつなげる、クエリでは一番つながってる辺が多かったものを出力する

 

今思ったのですが、この問題、グラフは全く関係なくて(N^2-N)/2桁のバイナリで情報復元能力を試すものです。つまり、QRコードの誤り訂正符号の仕組みを使えそうです。

 

少し調べてみましたがQRコードの誤り訂正はリード・ソロモン符号という方法で、めちゃくちゃこの問題に合っている気がします。これ理論値取れる説ありますが、、

しかしあまり理解はできていません。ハミング距離とか逆元とか競プロでつけた知識を総動員しそうです。

でも制限時間が5秒なのでこの方法は想定されてないような気がしますが...まあ時間はいくらでもあるのでとりあえずやってみます。

諦めて適当な貪欲をしてみたら結構いい点数がとれるっぽいので頑張ります。

一日目の結果

新方針:Nは適当に小さめにする、Nを7等分してiの2進数の桁ごとに分ける、1が多いか0が多いかで判断して予想する、で絶対スコア178085が限界でした。ここからは方針を根本的に変えないとだめそうです。

seed=10のビジュアライザ

明日やりたいこと:とりあえずABCで入緑する&天才解法を思い付く(漠然としている)

一つ考えているのは(N**2-N)/2bit表せるので0から2**((N**2-N)/2)までの数字をM等分して、ハミング距離が短いのを予想する、ということです。微妙な気がしますがやってみないとわからないのでやってみます。

明日はここに入緑した歓喜の気持ちを書き込むので対戦よろしくお願いします。

二日目

ええっと...とてつもない勘違いをしていました...

この制約を見逃していました。根本から制約を変える必要がありそうです。

14頂点のグラフで14*13/2をM等分して1の数をiごとに分ける感じでやったら絶対スコアが100倍程度になりました。昨日の苦労はなんやったんや...

かなり適当な貪欲でやったのでもう少し改善したいです。

一日ずっと頑張っていましたが140M程度までしか上がりませんでした。明日どうにか改善したいと思います。

三日目

とりあえずローカルテスタの自動実行をできるようにしました。

toolsディレクトリの中にmain.pyを入れて、run.batとcalc.pyファイルを作ります。run.batはこんな感じ

calc.pyはこんな感じです。


いろいろコードをこねくり回して期待値を計算するようにしてみたら200Mくらいまで点数が上がりました。ここから何もわからないのですが...たすけて...

四日目

何もわからない!撤退!!!!!!!!

感想

問題文がめちゃくちゃ難しくて誤読まみれでしたが間違いに気づいてコードを修正できたのはえらかったと思います。しかし、長期コンなのに4日目で撤退することになってしまい、自分の集中力のなさに絶望しました(?)。でもおそらくperfはヒューリスティックの中では自己べストになるでしょう。やったー!!