予鈴

競プロとか備忘録とか…

Mujin Contest D - うほょじご

解く前に考えていたこと

・絶対これ考察するだけでしょwと思ってたら違った。
・規則性を見出すことが全くできなかった。シュミレーションの選択肢を除外した。

正解

・解答を見ると閉路を探し出すような解答になっていたが、わからなかったのでメモ化再起で解いた。
 N \leq 999なので、2次元配列で状態を持てる。
・無限ループに突入する部分の判定が勉強になった。

  if(dp[x][y] != -INF)return dp[x][y];
        if(x == 0 or y == 0) return dp[x][y] = 0;
        dp[x][y] = 1;
        
        int tx = x, ty = y;
        if(tx < ty) tx = rev(x);
        else ty = rev(y);
        if(tx < ty)ty -= tx;
        else tx -= ty;
        
        return dp[x][y] = dfs(tx,ty);

まず、x,yに対し、dp[x][y]に1を一時的に格納する。
x,yに対して操作を行った後、閉路に突入する(つまり、x,yに戻ってくる)と、格納されている1が帰る。
そうでない場合は0が帰る。

#include<bits/stdc++.h>
using ll  = long long;
#define int ll
#define rep(i,n) for(int i=0;i<n;i++)
#define all(in) in.begin(),in.end()
#define INF (sizeof(int) == 4 ? (int)1e9:(int)1e18)
using namespace std;
template<typename Head, typename Value> auto vectors(const Head &head, const Value &v) { return vector<Value>(head, v); }
template<typename Head, typename... Tail> auto vectors(Head x, Tail... tail) { auto inner = vectors(tail...); return vector<decltype(inner)>(x, inner); }
auto rev(int t){
    string s = to_string(t);
    reverse(all(s));
    return stoll(s);
}
signed main(){
    int n,m; cin >> n >> m;
    auto dp = vectors(1024,1024,-INF);
    function<int(int,int)>dfs = [&](int x, int y) -> int{
       
        if(dp[x][y] != -INF)return dp[x][y];
        if(x == 0 or y == 0) return dp[x][y] = 0;
        dp[x][y] = 1;
        
        int tx = x, ty = y;
        if(tx < ty) tx = rev(x);
        else ty = rev(y);
        if(tx < ty)ty -= tx;
        else tx -= ty;
        
        return dp[x][y] = dfs(tx,ty);
    };
    int ans = 0;
    rep(i,n+1)rep(j,m+1){
        if(i and j) ans += dfs(i,j);
    }
    cout << ans << endl;
}

ABC106 AtCoder Express 2

解く前に考えていたこと

区間を数える典型なので、解き方は雰囲気で知ってた(実装はしてません)。
実装の際に盛大にバグらせたので戒めとして書きました。

解法

BIT+ クエリ先読みのオフラインアルゴリズムというらしい

#include<bits/stdc++.h>
using ll  = long long;
#define rep(i,n) for(int i=0;i<n;i++)
#define erep(e,v) for(auto && e :v)
#define all(in) in.begin(), in.end()
#define INF (sizeof(int) == 4 ? 1e9:1e18)
using namespace std;
template<class T> void join(T a){for(auto itr :a){if(itr != *a.begin())cout << " "; cout << itr;} }
using pii = pair<int,int>;
using piii = pair<int,pii>;

int bit [1000] = {},n;
int sum(int i){
    int s = 0;
    while (i > 0) {
        s += bit[i];
        i -= i & -i;
    }
    return s;
}

void add (int i,int x){
    while (i <= n) {
        bit[i] += x;
        i += i & -i;
    }
}
signed main(){
    int m,q; cin >> n >> m >> q;
    vector<piii>v( m + q);
    rep(i,m+q) {
        int a,b; cin >> a >> b;
        piii temp;
        temp.first = a, temp.second.first = b;
        temp.second.second = i;
        v[i] = temp;
    }
    sort (all(v),[](auto const x,auto const y) {
        return (
                x.second.first < y.second.first  or
                (x.second.first == y.second.first and x.second.second < y.second.second)
                );
    });
    int ans[100000] = {};
    erep(e,v){
        int left = e.first;
        int right = e.second.first;
        bool isQuery = e.second.second >= m;
        int index = e.second.second - m;
        if (isQuery){
            ans[index] =  sum(right) - sum (left - 1);
        } else {
            add(left, 1);
        }
    }
    rep(i,q) cout << ans[i] << endl;
}

9/6 追記
一応segtreeの解答も載せておこうかな
https://beta.atcoder.jp/contests/abc106/submissions/3096327

ABC038 D - プレゼント

問題概要

D - プレゼント

解く前に考えていたこと

AOJのマトリョーシカと非常に似ている。
・ただし、この問題は制約がN \leq10^5O(N^2)解は部分点しか取れない。
・LISであることは間違えないので、比較関数を書き換えて蟻本にある N log N のLISを書くがWA
・途中でpair型dpを更新していた。

正解

まず前提として、プレゼントaがプレゼントbの中に梱包される条件とは、
 w_b>w_a \land h_b > h_a  が成立する必要がある。
したがって、wを昇順にソートして h に関してLISを考えればうまくいきそう。
ここで、pairでsortすると、同値のwに関して、hは昇順に並ぶこととなる。これが良くない。

i番目の要素でLISの配列を上書きする場合を考える。ただし、上書きされる要素はj番目( j < i)とする。
・このとき、w_i = w_jが成り立っていると、h_i < h_jとなっているため、上書きできない。(直感的に説明すると、j番目の要素の方がi番目の要素より条件が厳しい)

逆に言えば、w_i = w_jのときh_i > h_jとなっていれば、通常通り更新することができる。

#include<bits/stdc++.h>
using ll = long long;
#define int ll
#define rep(i,n) for(int i=0;i<n;i++)
#define all(in) in.begin(), in.end()
#define INF (sizeof(int) == 4 ? 1e9:1e18)
using namespace std;
using pii = pair<int,int>;
auto f = [](pii a,pii b){
    return !((a.first == b.first and b.second> a.second) or a.first > b.first);
};
signed main(){
    int n; cin >> n;
    vector<pii>v(n);
    for(auto && e:v)cin >> e.first >> e.second;
    sort(all(v),f);
    vector<int>dp(n,INF),lis;
    for(auto && e:v)lis.emplace_back(e.second);
    rep(i,n){
        *lower_bound(all(dp),lis[i]) = lis[i];
    }
    cout << (int)(lower_bound(all(dp),INF)-dp.begin()) << endl;
}

VimでIndentlineが動かなかった話

VimでIndentlineが動かなったのでメモ。
調べてみると、githubのIssueで報告されていました。
github.com
以下日本語でもメモを残しておきます。
まずターミナルで

vim --version

を実行します。すると、以下のような画面が現れるはずです。

f:id:yebityon:20180707170230p:plain

ここで、-conceal となっているのが問題です*1vimを入れ直しましょう。(画像では+になっています。)

brew install  vim

を実行して新しいVimを入れ直します。後は.bashrcにaliasを貼ると完成です。
以下の記事を参考にしていただけるとわかりやすいと思います。
qiita.com
qiita.com

*1:おそらくOS Xにもとから入ってるVimをお使いの方はほとんど-になっているハズです。

ICPC 2018国内予選

国内予選に後輩(わたあめくん)と先輩とともに参加しました。

開始1時間前

大雨の影響で大学までの電車がなかったので、大阪の別キャンパスで参加することになる。

yebiが到着 → アナウンス「全館4時半で閉館です☆」(は?)

わたあめくんと合流して、帰宅難民向けに開放された食堂か近くのイ○ンの中にあるカフェのどちらかで参加しようとする。

先輩と合流してカフェのほうがおしゃれなのでカフェですることになる。このとき16:20。なんだこれは。

 

f:id:yebityon:20180707125131j:plain

 

コンテスト

・開始してC問題を見る。累積和見るだけでしょとなる。

・気がつくと後輩がAを通す。プロ

・後輩にCを説明すると、累積を見るだけではヤバイことがわかる。bの制約的にしゃくとり法も間に合わなさそうで考察が振り出しに戻る。

・後輩がトイレに行ってる間に、bがN個に分割できるかどうか判定できることに気づく。後輩に伝えて実験してみると行けそうとなったので方針が固まる。

・Bが虚無そうだった。途中でBと交代してCを書く。5分ぐらいで書いてAC。

・先輩にBを任せてDを二人で考える。

・なんやこの制約、どうせ全探索やろ。

・いいえ、後輩くんに実装を任せてサンプルを試すも、実行時間が長すぎる。でもDPできなさそうなので迷う。

・よく考えると、勝利数は(N-1)/2の場合しかないことに気づく。枝刈りする。

・それでも間に合わない。

・先輩がBを通す。プロ

・Dをいろいろ考えてるうちにコンテストが終わった。

感想

後輩、先輩と自分のレートが大きく離れているので、自分の考察力や実装力で劣るのではないかと考えることが多々ありました(実際その場面はあった。)

ただ、自分の考察の甘いところをすぐに指摘してくれたり、ペアプロする上で、後輩・先輩のコーディングを見ることはとても勉強になりました。チームを組んでくれた先輩・後輩にとても感謝しています。

vectorsのテンプレートを詳しく見る

はじめに

競プロやってると、多次元のvectorをよく使うことがあります。非常に便利なんだけど、宣言がめんどくさい。
例えば、3次元のvectorを宣言しようとすると、

    vector<vector<vector<int>>>v(10,vector<vector<int>>(10,vector<int>(10,1000)));

こうなります。
4次元はもう書く気すらなくなってしまいます。しかしvectorsのテンプレートを用いると、

 auto v = vectors(10,10,10,3);

このように簡単に書くことができます。
N+1個の引数に対して、最後の数値を初期値としてN次元のvectorを生成できます。
今回はこのテンプレートを詳しく見ていくことにします(勉強を兼ねて)

vectorsテンプレート

template<typename Head, typename Value>
 auto vectors(const Head &head, const Value &v) {
            return vector<Value>(head, v);
 }

template<typename Head, typename... Tail>
 auto vectors(Head x, Tail... tail) { 
       auto inner = vectors(tail...);
   return vector<decltype(inner)>(x, inner); }

vectorsのテンプレートは2つのvectorsという関数から成り立っています。
これを詳しく見ていきます。

可変引数テンプレート

任意のNについてvectorを生成するので、引数は毎度変化します。
これに対応するために、可変引数テンプレート*1が用いられています。
サンプルコードを見て見ましょう。

#include<bits/stdc++.h>
using namespace std;
template<class ...Args>
void func(Args... arg){
   //argの数を表示
    cout << sizeof...(arg) << endl;
}
template<class Head, class ...args> void func2 (Head a,args... b){
    cout << a  << endl;
    cout << sizeof...(b) << endl;
}
signed main(){
    func(1,2,3,4,5,6,7,8);
    func2(1,2.2,"3",'4');
   /* output:
      8
      1
      3
   */
}

可変引数テンプレートでは、任意のと任意の引数をテンプレートとして利用することができます。
上記のfunc関数では
・Argsがテンプレートパラメータパック
・argsをが関数パラメータパック
となっています。それぞれ、推論された引数が格納されています。
関数内でパラメータパックを用いる際は ... を用いて展開します。
func2に注目してみると、引数として渡された引数が、Head 型の a と args型のbに分割されています。f:id:yebityon:20180609151625p:plain

この関数を繰り返し用いると、やがてargs...の引数は一つになります。
勘がいい方はすでに気づいているかもしれませんが、これは繰り返し処理に用いることができます。
具体例を示します。

#include<bits/stdc++.h>
using namespace std;
template<class Head, class args>    void func2 (Head a,args b){
    cout << a << endl;
    cout << b << endl;
}
template<class Head, class... args> void func2 (Head a,args... b){
    func2(b...);

}signed main(){;
    func2(1,2.2,"3",'4');
   /*output
    3
    4
   */

}

複数の引数が渡された関数は、再帰的にfunc2関数を呼び、最後に引数の引数の数は2つになります。
このように、関数をオーバーロードすることで、タグディスパッチによって最適な関数が呼び出されます。

decltypeについて

上記の知識とautoがsyntax sugarであることがわかれば、ほとんど中身が理解できる…かもしれませんが、最後にdecltypeだけ説明しておきます。
decltypeとは、指定した式から型取得する機能です。

#include<bits/stdc++.h>
using namespace std;
signed main(){
    cout << sizeof(decltype(2.2+ 3.4)) << endl; // decltypeはdouble型
    cout << sizeof(decltype('a' )) << endl;  //decltypeはchar 型
    cout << sizeof(decltype(2+1))<<endl; //decltypeはint型
    /* output
     8
     1
     4
     */
}

vectorsテンプレートでは、decltypeで取得した型とするvectorを返すことで、入れ子構造を達成しています。

*1:任意の数でテンプレートを引数をとれるテンプレートを宣言できる機能

B - Construct Sequences

B - Construct Sequences

解く前に考えてたこと

・配列の要素数を超える大きい公差を持った等差数列を考えて、順列Pによる差を相殺したい。
・具体例を考えるも、一般化できずに死亡、特に、入力n の値によって、数列を構築しようとしたのが問題かもしれない。

正解

M = 40000
A_i = M(i+1)
B_i = M(n-i-1) などとすると、 A_i +B_i = M(n)となる。つまり、添字iの値によらず一定である。
問題はA_i + B_iもまた単調に増加する数列とする必要があるので、A_{P_i}の添字にiを足してやる。Mが巨大なので、問題なく数列を構築することができる。

#include<bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;i++)
#define loop(i,s,n) for(int i=s;i<n;i++)
#define all(in) in.begin(), in.end()
#define rep(i,n) for(int i=0;i<n;i++)
#define loop(i,s,n) for(int i=s;i<n;i++)
#define all(in) in.begin(), in.end()
#define MP make_pair
#define INF (sizeof(int) == 4 ? 1e9:1e18)
#define EPS 0.0000000001
using namespace std;
template<class T, class S> void cmin(T &a, const S &b) { if (a > b)a = b; }
template<class T, class S> void cmax(T &a, const S &b) { if (a < b)a = b; }
template<typename Head, typename Value> auto vectors(const Head &head, const Value &v) { return vector<Value>(head, v); }
template<typename Head, typename... Tail> auto vectors(Head x, Tail... tail) { auto inner = vectors(tail...); return vector<decltype(inner)>(x, inner); }
template<class T> void join(T a){int b=0;for(auto itr :a){if(b++!=0)cout << " "; cout << itr;} }
using ll  = long long;
using ld  = long double;
using pii = pair<int,int>;
using piii = pair<int,pii>;
int W,H;
int dx[]={0,0,1,-1}, dy[]={1,-1,0,0};
bool valid(int x,int y){return (0<=x&&x<W)&&(0<=y&&y<H);}
#define int ll
signed main(){
    int n; cin >> n;
    vector<int>p(n);  rep(i,n) cin >> p[i];
    vector<int>a(n),b(n);
    int maxi = 40000;
    rep(i,n){
        a[i] = maxi*(i+1);
        b[i] = maxi*(n-i-1)+1;
    }
    rep(i,n){
        a[p[i] - 1] += i;
    }
    join(a); cout << endl;
    join(b); cout << endl;
}
感想

書いてみると意外と解けそうな気がする。ちなみにWAを量産したのはf:id:yebityon:20180527130538p:plain
この制約を見落としていたため。
M = 1e7 などとすると死にます。