ながめも

競技プログラミングについて

GCJ2020 B - Pascal Walk

B - Pascal Walk

問題へのリンク

和が Nになるようにパスカルの三角形上を動く問題。

解説

以降、 N \gt 30とする。

パスカルの三角形の行の和は二冪であることから、 Nを2進数表現すればうまく表せそうである。ここで、r行に到達したいだけでも、その手前の行を訪れなければ到達できないということに注意したい。

この解決策として、少なくとも30行目までは到達すると仮定してしまうことである。このとき、必ず1列目を通ると仮定すると、 30は必ず確保することができるので、それ以外のところでadditionalな和 N - 30を揃えたくなる。すでに1行目の 1が使われていることから、 r行目を選ぶ場合、追加で足されるのは 2^{r-1} - 1である。 つまり、選ぶ行の集合を Rとすると、

 N - 30 = \sum_{r\in{R}}(2^{r-1} - 1)

これは非常にbit表現に似ているが、惜しい。シグマの中身が -1されてしまっており、うまく表現できなさそうである。ここで、30行が終わったあと、集合 Rのサイズ分だけさらに下に行を増やすことで解決することができる。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,N) for(int i=0;i<int(N);++i)
#define rep1(i,N) for(int i=1;i<int(N);++i)
#define bit(k) (1LL<<(k))

void solve(){
    int N;
    cin >> N;
    if(N < 30){
        rep1(i,N+1){
            cout << i << " " << 1 << endl;
        }
        return;
    }
    N -= 30;
    int isleft = 1;
    int cnt = 0;
    for(int r = 1; r <= 30;r++){
        if(N & bit(r-1)){
            cnt++;
            if(isleft){
                for(int col = 1; col <= r;col++){
                    cout << r << " " << col << endl;
                }
            }
            else{
                for(int col = r; col >= 1;col--){
                    cout << r << " " << col << endl;
                }
            }
            isleft ^= 1;
        }
        else{
            cout << r << " " << (isleft ? 1: r) << endl;
        }
    }
    for(int k = 1; k <= cnt;k++){
        cout << 30 + k << " " << (isleft ? 1: 30 + k) << endl;
    }
}
int main() {
    int t;
    cin >> t;
    rep1(i, t+1){
        cout << "Case #" << i << ":" << endl;
        solve();
    }
}