gotutiyan’s blog

競技プログラミングをやったりopenframeworksでお絵かきをしたりしています。

yukicoder No.607 開通777年記念

問題
電車の乗り降りが行われる中で、任意の連続する車両の区間における、乗車数の和が777人となるタイミングがあるかどうかを判定する。
自分で作った問題なので、真面目に解説をします。

解法
簡潔にいうと、乗車人数を累積和を用いて更新していきながら、更新する度に尺取り法で777人となるような連続する車両の区間があるかどうか判定する。
入力のデータは、縦M行、横N列であることに注意しよう。

手始めに、0で初期化した各車両の人数を格納する長さNの配列を用意する。

以下の操作は、M回のループを回す度に行っていく。(つまり、乗客の乗り降りが行われる度に行う。)
まずは乗車人数を更新していくことを考える。入力は一つ前の区間との差分になっているので、N個の入力を受けて、i個目の入力はi番目の配列に足すだけで良い。
次に777人の判定をしていくことを考える。今回は尺取り法での解法を紹介する。

尺取り法は、範囲の右端と左端を表す変数を用意して、範囲における特定の値が大きければ左端を進めて範囲を狭くして、特定の値を小さくする。小さくなれば、また右端を伸ばして、特定の値を調べることをする。この問題では、「範囲の要素全ての和」が特定の値に当たる。
左端と右端を表す変数left, rightを作り、rightをループで回しながら(範囲の右端を伸ばしながら)、範囲の合計が777より大きい間、leftをwhileで縮めていく(範囲の左端を縮める)。whileを抜けた時、範囲の合計は777以下になることが保証されているので、このタイミングで777人ぴったりになっているかを判定すれば良い。
また、このwhileではleft>rightとならないように注意しよう(左端と右端の位置関係より当然)。

今回の尺取り法はrightをfor文で、leftをwhileで操作したが、両方whileでやったりする人もいるので、自分が書きやすいやり方を見つけよう。

int main() {
    int N,M;
    cin>>N>>M;
    vector<int> train(N,0);  //電車の乗車数を保存する配列train[ ]
    rep(i,0,M){
        //乗車人数の更新
        rep(j,0,N){
            int x;cin>>x;
            train[j]+=x;
        }
        //尺取り法
        int left=-1,right=0,sum=0;
        for(int right=0;right<N;right++){  //右端を進めるfor文
            sum+=train[right];
            while(sum>777&&left<right){  //左端を縮めるwhile文
                left++;
                sum-=train[left];
            }
            //合計が777人であればYESを出力してプログラム終了
            if(sum==777){
                cout<<"YES"<<endl;
                return 0;
            }
        }
    }
    cout<<"NO"<<endl;
    return 0;
}