接着剤の精進日記

競プロでの精進や研究に関係したことを書いていきます。

Codeforces Round #634 (Div. 3)

codeforces.com

A. Candies and Two Sisters

問題文

n個のキャンディをAliceとBettyで以下の条件を満たすように分ける
1. Aliceがキャンディをa個貰う( a \gt 0 )
2. Bettyがキャンディをb個貰う( b \gt 0 )
3. AliceはBettyよりも多くキャンディを貰う ( a \gt b )
4. すべてのキャンディを2人のどちらかが必ず貰う( a + b = n)
これらをすべて満たすような分け方の数を数えよ

解法

\frac{n-1}{2}を出力する
提出コード

void solve(){
    ll n;
    cin >> n;
    cout << (n - 1) / 2 << endl;
}

B. Construct the String

問題文

正の整数n, a, bが与えられる
長さがnの文字列を構築することを考える
ただし、構築した文字列の長さがaの全ての部分文字列にb個の異なる文字を含むような文字列を1つ出力せよ

解法

長さがaの文字列をabcddd...のようにb個の異なる文字からなるよう適当に作る
その後a個前の文字を順に末尾にくっつけていけばいい

提出コード

void solve(){
    int n, a, b;
    cin >> n >> a >> b;
    char c = 'a';
    string ans = "";
    for(int i=0;i<a;i++){
        if((c - 'a' + 1) < b){
            ans += c;
            c++;
        }
        else ans += c;
    }
    for(int i=a;i<n;i++) ans += ans[i-a];
    cout << ans << endl;
}

C. Two Teams Composing

問題文

n人の生徒から丁度2つのチームを作りたい
i番目の生徒の持っているskillがa_iで与えられる
一つ目のチームの生徒のskillは全て異なる
二つ目のチームの生徒のskillは全て同じ
このような条件を満たすように丁度2つのチームを同じ人数となるように作りたい
この時、作ることが可能なチーム人数の最大を答えよ

解法

重複無しのskillの個数(distinct)と、同一skillの最大数(ma)を数える
distinct - 1がmaよりも大きかったらmaまでしか作れない
ma - (distinct - 1 ) が 2よりも大きかったら distinct + 1まで、そうでなければdistinctまでしか作れない
提出コード

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    vector<int> cnt(n+1);
    REP(i,n){
        cin >> a[i];
        cnt[a[i]]++;
    }
    if(n == 1){
        cout << 0 << endl;
        return;
    }

    int distinct = 0, ma = 0;
    REP(i,n) if(cnt[i+1]){
        distinct++;
        chmax(ma, cnt[i+1]);
    }
    distinct--;
    if(distinct >= ma) cout << ma << endl;
    else{
        ma -= distinct;
        if(ma >= 2) cout << distinct + 1 << endl;
        else cout << distinct << endl;
    }
}

D. Anti-Sudoku

問題文

9 \times 9数独パズルの正答が与えられる
これらの1要素を最大9回まで変更することで以下の条件を満たすようにする
1. 全てのマスの要素は1以上9以下
2. 各列は同じ要素を最低2つ持つ
3. 各列は同じ要素を最低2つ持つ
4. 各3 \times 3ブロックに同じ要素を最低2つ持つ
このような条件を満たす答えが必ず存在することが保証されるので、そのうち1つを出力せよ

解法

ギャグ…
各列・各行がそれぞれ独立となるようにマス9個を選ぶ
そのマスに対して今書かれている数字+1に置き換える
これで終わりなんだけど、1が書かれているマスを2に書き換えるとかでも大丈夫だね(これTLで見たけど天才すぎる)
提出コード

void solve(){
    vector<string> fi(9);
    REP(i,9) cin >> fi[i];

    if(fi[0][0] == '9') fi[0][0] = '1';
    else fi[0][0]++;
    if(fi[1][4] == '9') fi[1][4] = '1';
    else fi[1][4]++;
    if(fi[2][8] == '9') fi[2][8] = '1';
    else fi[2][8]++;
    if(fi[3][1] == '9') fi[3][1] = '1';
    else fi[3][1]++;
    if(fi[4][5]== '9') fi[4][5] = '1';
    else fi[4][5]++;
    if(fi[5][6] == '9') fi[5][6] = '1';
    else fi[5][6]++;
    if(fi[6][2] == '9') fi[6][2] = '1';
    else fi[6][2]++;
    if(fi[7][3] == '9') fi[7][3] = '1';
    else fi[7][3]++;
    if(fi[8][7] == '9') fi[8][7] = '1';
    else fi[8][7]++;

    REP(i,9){
        REP(j,9) cout << fi[i][j];
        cout << '\n';
    }
}

E2. Three Blocks Palindrome (hard version)

問題文

E1は制約だけが違うので省略
n個の正の整数からなる数列aが与えられる
three blocks palindromeを定義する
three blocks palindromeは最大でも2つの異なる要素からなる次のような配列
f:id:tkm-kyudo:20200415154251p:plain
x, yは0以上の整数
数列aの部分列(連続じゃなくても良い)のうち、three blocks palindromeを満たす最大の長さを求めよ

解法

使う文字列の最大が高々2つまでなので全探索することを考える
各文字に対して、その文字のindexと累積和を取っておく
その後、どの文字を何個両端に使うかを探索し、間に入れられる文字とその個数を全探索する
これ計算量見積もりづらいね
提出コード

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    REP(i,n){
        cin >> a[i];
        --a[i];
    }
    vector<vector<int>> idx(200);
    vector<vector<int>> cum(200, vector<int>(n+1));
    REP(i,n){
        REP(j,200) cum[j][i+1] = cum[j][i];
        cum[a[i]][i+1]++;
        idx[a[i]].emplace_back(i);
    }
    int ans = 0;
    REP(i,200){
        int sz = idx[i].size();
        chmax(ans, sz);
        if(sz <= 1) continue;

        REP(j,sz){
            if(idx[i][j] >= idx[i][sz-j-1]) break;
            int len = 0;
            REP(k, 200){
                chmax(len, cum[k][idx[i][sz-j-1]] - cum[k][idx[i][j]+1]);
            }
            chmax(ans, (j+1) * 2 + len);
        }
    }
    cout << ans << endl;
}

おわりに

E誤読して一生問題文の理解が出来なくて終わってしまった

Codeforces Round #633 (Div. 2)

codeforces.com

A. Filling Diamonds

問題文

正の整数nが与えられる
図の左側の図形を移動、回転させて右側の図に敷き詰めることを考える
この時、その敷き詰め方の個数を数えよ

解法

難しくないですか・・・?
実は、どこかを縦に置くと残りは一意に定まる
そのため、縦に置く場合の数はn通りあるのでnを出力する
提出コード

void solve(){
    int n;
    cin >> n;
    cout << n << endl;
}

B. Sorted Adjacent Differences

問題文

n個の要素からなる数列aが与えられる
|a_1 - a_2| \leq |a_2 - a_3| \leq ... \leq |a_{n-1} - a_n|を満たすように数列を並べかえたものを出力せよ
これを満たす数列の並べ方は必ず存在する

解法

数列をソートし、左端と右端を交互に取っていけば必ず達成できる
何故か実装バグらせてしまったのでdequeを使いました…
端から取っていくと単調減少になっているのでreverseして出力する

提出コード

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    REP(i,n) cin >> a[i];
    sort(a.begin(), a.end());
    deque<int> dq;
    REP(i,n) dq.push_back(a[i]);
    int parity = 0;
    vector<int> ans;
    while(!dq.empty()){
        if(parity & 1){
            ans.push_back(dq.back());
            dq.pop_back();
        }
        else{
            ans.push_back(dq.front());
            dq.pop_front();
        }
        parity = 1 - parity;
    }
    reverse(ans.begin(), ans.end());
    REP(i,n) cout << ans[i] << " ";
    cout << endl;
}

C. Powered Addition

問題文

長さnの数列aが与えられる
x秒目に次の操作を行える
1. 任意のインデックスi_1, i_2, ... , i_kを選びそれぞれの要素を a_{i_j} = a_{i_j} + 2^{x-1}に更新する
ただし、インデックスを一つも選ばなくても良い
このような操作を行った時、数列aが広義単調増加列にするための最小の秒数を答えよ

解法

こどふぉによくあるやつ
前から貪欲に変更していけばいい
今見ている要素に加算する必要がある場合を考える
一つ前の要素の差を2進数で考えるとその表し方は一意になる
そのため、一つ前の要素と同じにするのが最適となる
そのような最小の秒数は差の2進数の先頭bitを見ればいいので数列全体でのこのmaxが答え
提出コード

void solve(){
    int n;
    cin >> n;
    vector<ll> a(n);
    REP(i,n) cin >> a[i];
    
    int ma = 0;
    for(int i=1;i<n;i++){
        if(a[i-1] <= a[i]) continue;
        ll sa = a[i-1] - a[i];
        for(int i=40;i>=0;i--){
            if(sa >> i & 1){
                chmax(ma, i + 1);
                break;
            }
        }
        a[i] = a[i-1];
    }
    cout << ma << endl;
}

D. Edge Weight Assignment

問題文

n頂点からなる重みなしの木が与えられる
この木の各辺に重みを与えることを考える
この時、任意の2つの葉からなるパス上の重みのXORを取った時に0となるようにしたい
割り当てた異なる重みの個数を考えたときに、その最小と最大を答えよ

解法

解いたら載せます

おわりに

div2 A, B難しすぎる

AtCoder Beginner Contest 162(ABC162)

atcoder.jp

4完13:30でパフォ1500ちょっと
1500取ってもレートが10とかしか上がらなくなってきたね

A - Lucky 7

N mod 10\lfloor \frac{N}{10} \rfloor mod 10 \lfloor \frac{N}{100} \rfloor mod 10が7になるか見ればいい
文字列でやったほうが楽だったかな
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    if(N % 10 == 7 || (N / 10) % 10 == 7 || (N / 100) == 7) cout << "Yes" << endl;
    else cout << "No" << endl;
}

B - FizzBuzz Sum

ループで愚直に足していっても間に合うので愚直にやる
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    ll sum = 0;
    for(int i=1;i<=N;i++){
        if(i % 3 == 0 || i % 5 == 0) continue;
        sum += i;
    }
    cout << sum << endl;
}

C - Sum of gcd of Tuples (Easy)

200^3なら余裕で間に合うので3重ループで求める
gcd(a, b, c)gcd(gcd(a,b), c))で求められる
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int K;
    cin >> K;
    ll ans = 0;
    for(int i=1;i<=K;i++){
        for(int j=1;j<=K;j++){
            for(int k=1;k<=K;k++){
                ans += gcd(gcd(i,j), k);
            }
        }
    }
    cout << ans << endl;
}

D - RGB Triplets

これ緑difficulty行かないんだね、すごいなあ
条件よりS_i \neq S_jなのでiとjを全探索することを考える
そうすると S_i \neq S_k and S_j \neq S_kを満たすものを高速に数えられればいい
これは累積和を使うことで可能となる
ただし j - i \neq k - jという条件があるので、S_{j + ( j - i)}が条件を満たしていた場合、その分を引いて上げる必要がある
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    string S;
    cin >> N >> S;
    vector<ll> cumR(N+1), cumG(N+1), cumB(N+1);
    REP(i,N){
        cumR[i+1] = cumR[i] + (S[i] == 'R');
        cumG[i+1] = cumG[i] + (S[i] == 'G');
        cumB[i+1] = cumB[i] + (S[i] == 'B');
    }
    ll ans = 0;
    REP(i,N){
        for(int j=i+1;j<N-1;j++){
            if(S[i] == S[j]) continue;
            if(S[i] != 'R' && S[j] != 'R'){
                ans += cumR[N] - cumR[j];
                if(j + j - i > N) continue;
                if(S[2*j - i] == 'R') ans--;
            }
            if(S[i] != 'G' && S[j] != 'G'){
                ans += cumG[N] - cumG[j];
                if(j + j - i > N) continue;
                if(S[2*j - i] == 'G') ans--;
            }
            if(S[i] != 'B' && S[j] != 'B'){
                ans += cumB[N] - cumB[j];
                if(j + j - i > N) continue;
                if(S[2*j - i] == 'B') ans--;
            }
        }
    }
    cout << ans << endl;
}

E - Sum of gcd of Tuples (Hard)

これ難しい
まずgcdがk( 1 \leq k \leq K) となる場合の数を数えたい
ただし、kを数える時kの倍数の分を引いて上げる必要がある
そのためには、上から個数を保存しながら数えていき、その分を引いてあげればいい
本番ではここまでは考えられていたけど、包除する分がずっと上手く行かないまま終わってしまった
kの倍数の個数のカウントと、場合の数のカウントを同時にやろうとしてたから沼にハマってたっぽい
終わった後他の人の実装見ながら別々にやってあげるとすっきりしたコードでAC

提出コード

using mint = Fp<MOD>;

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, K;
    cin >> N >> K;

    vector<ll> cnt(K + 1, 1);
    for(int i=1;i<=K;i++){
        for(int j=2*i;j<=K;j+=i) cnt[i] += cnt[j];
    }
    vector<mint> res(K + 1);
    for(int i=K;i>=1;i--){
        res[i] += modpow(mint(cnt[i]), N);
        for(int j=2*i;j<=K;j+=i) res[i] -= res[j];
    }
    mint ans = 0;
    for(int i=1;i<=K;i++) ans += mint(i) * res[i];
    cout << ans << endl;
}

F - Select Half

本番中はチラ見しただけで終わった
パッと見た感じ貪欲だと死にそうでDPしないといけなさそうな見た目だった
良い感じのDPらしいのでちゃんと解いておきたい

おわりに

1500取っても微増しかしないの直感に反しちゃうね
青パフォ安定して取れるようにしたいなあ(青difficultyを本番で通したい)

Codeforces Round #632 (Div. 2)

codeforces.com

A. Little Artem

問題文

n \times mのボードがあり、各マスは白か黒の色で塗られている
隣接する白マスが一つでもある黒マスの個数をB
反対に、隣接する黒マスが一つでもある白マスの個数をWとする
この時、 B = W + 1を満たすボードの色の塗り方を1つ出力せよ

解法

Aに置かれているので当然ギャグなんですが時間使いすぎた
1行目と1列目を全部黒に塗ってそれ以外は白に塗る
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int Q;
    cin >> Q;
    while(Q--){
        int n, m;
        cin >> n >> m;
        vector<string> fi(n, string(m, '.'));
        REP(i,n) fi[i][0] = 'B';
        REP(j,m) fi[0][j] = 'B';
        REP(i,n) REP(j,m) if(fi[i][j] == '.') fi[i][j] = 'W';
        REP(i,n) cout << fi[i] << endl;
    }
}

B. Kind Anton

問題文

n個の要素からなる数列a, bが与えられる
以下の操作を任意の回数行い、aをbに一致させられるかどうかを判定せよ
1. 1 \leq i \lt j \leq n を満たすペア  ( i, j )を選ぶ
1.  a_ja_i + a_jに変更する

解法

後ろから貪欲していくといい
今見ている数より前に1と-1がいくつあるかの情報を持っておき
a_ib_iに出来ないものがあればその時点でNOになる

提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int Q;
    cin >> Q;
    while(Q--){
        int n;
        cin >> n;
        vector<int> a(n), b(n);
        int posi = 0, nega = 0;
        REP(i,n){
            cin >> a[i];
            if(i < n-1){
                if(a[i] > 0) posi++;
                else if(a[i] < 0) nega++;
            }
        }
        REP(i,n) cin >> b[i];
        bool ok = true;
        if(a[0] != b[0]) ok = false;
        for(int i=n-1;i>0;i--){
            if(i < n-1){
                if(a[i] < 0) nega--;
                if(a[i] > 0) posi--;
            }
            if(a[i] == b[i]) continue;
            if(b[i] > a[i] && posi > 0) continue;
            if(b[i] < a[i] &&  nega > 0) continue;
            ok = false;
            break;
        }
        if(ok) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
}

C. Eugene and an array

問題文

長さnの数列aが与えられる
aの連続する部分列のうち、部分列の連続する部分和に0となるものが存在しない時
その部分列を良い配列と呼ぶ
数列aの部分列のうち、良い配列の個数を求めよ

解法

Zero-Sum Rangesっぽくやる
ある区間の部分和が0になるには、累積和が0もしくは、すでに出現した値になる必要がある
そのため、左端を固定しながら区間の右端を伸ばしていき、累積和がすでに出てきた値なら左端を更新し、数え上げていく
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    ll n;
    cin >> n;
    map<ll, ll> mp;
    mp[0] = 0;
    ll left = -1;
    ll sum = 0;
    ll ans = 0;
    REP(i,n){
        ll a;
        cin >> a;
        sum += a;
        if(mp.count(sum)) chmax(left, mp[sum]);
        ans += i - left;
        mp[sum] = i + 1;
    }
    cout << ans << endl;
}

D. Challenges in school №41

問題文

n人の子どもたちが1列に並んでおり、右(R)もしくは左(L)を向いている
1秒ごとに子供たちが向かい合っているペアを任意の数だけお互いの向きを反対にすることが出来る
ただし、1秒間に最低1つのペアを反転させる必要がある
この時、丁度k秒で向かい合っているペアを0に出来るかどうかを判定し、可能ならばその操作を出力せよ

解法

1秒の間に可能なペアをすべて反転させることを考える
その時、操作を行う必要のある回数kを超えていたら不可能
また、操作を行えるペアの個数がk未満だったら不可能
それ以外は操作を行うことが可能である
この時、kに合わせるために、その差分だけ1秒間に1つのペアを出力して合わせてあげればいい
終わった後TLで見かけたけど、'L'と'R'を数値とみなすと隣接swapの操作の最大は転倒数になる(天才か?)
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n, k;
    string S;
    cin >> n >> k >> S;
    vector<vector<int>> ans;
    int cnt = 0;
    REP(i,n + 10){
        vector<int> tmp;
        for(int j=0;j<n;j++){
            if(S[j] == 'R' && S[j+1] == 'L'){
                tmp.push_back(j);
                swap(S[j], S[j+1]);
                j++;
            }
        }
        if(tmp.size() == 0) break;
        cnt += tmp.size();
        ans.emplace_back(tmp);
    }
    // cout << S << endl;
    // cout << cnt << " " << ans.size() << endl;
    if(cnt < k || (int)ans.size() > k){
        cout << -1 << endl;
        return 0;
    }
    k -= (int)ans.size();
    REP(i, ans.size()){
        while(k > 0 && (int)ans[i].size() > 1){
            cout << 1 << " " << ans[i].back() + 1 << '\n';
            ans[i].pop_back();
            k--;
        }
        cout << ans[i].size() << " ";
        for(auto x : ans[i]) cout << x + 1 << " ";
        cout << '\n';
    }
}

おわりに

Cで頭壊れてしまった
D復習して無限にTLEになる〜ってなってたけどendlが原因だった(それはそう)

Judge System Update Test Contest 202004

atcoder.jp

ジャッジシステムアップデート!
c++17をようやく使い始めることになりそう(構造化束縛が今の所一番うれしいかな)

A - Walking Takahashi

FA狙おうとしたけど読解でぐだって終わり
最初にいる地点で場合分け
c++17だとclamp使う向け問題っぽい
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int S, L, R;
    cin >> S >> L >> R;
    if(S < L) cout << L << endl;
    else if(R < S) cout << R << endl;
    else cout << S << endl;
}

B - Picking Balls

pairでソートが想定かなあと思いつつもRとBに分けて別々にソートした
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    vector<int> B, R;
    REP(i,N){
        int X;
        char c;
        cin >> X >> c;
        if(c == 'B') B.push_back(X);
        else R.push_back(X);
    }
    sort(B.begin(), B.end());
    sort(R.begin(), R.end());
    for(auto x : R) cout << x << endl;
    for(auto x : B) cout << x << endl;
}

C - Numbering Blocks

これ難しい
最初数学できるかなって10分くらい考えてたけど全然わからなかった(フック長の定理が使えるとかなんとか)
要素が最大でも9個までなのでnext_permutationで全探索して条件を満たすものを数えればいい
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int a1, a2, a3;
    cin >> a1 >> a2 >> a3;
    ll ans = 0;
    vector<int> perm(a1+a2+a3);
    iota(perm.begin(), perm.end(), 1);
    do{
        vector<vector<int>> X(3);
        REP(i,a1) X[0].push_back(perm[i]);
        REP(i,a2) X[1].push_back(perm[i+a1]);
        REP(i,a3) X[2].push_back(perm[i+a1+a2]);
        bool ok = true;
        REP(i,3){
            for(int j=0;j+1<X[i].size();j++){
                if(X[i][j] > X[i][j+1]) ok = false;
            }
        }
        REP(i,2){
            for(int j=0;j<min(X[i].size(), X[i+1].size());j++){
                if(X[i][j] > X[i+1][j]) ok = false;
            }
        }
        if(ok) ans++;
    }while(next_permutation(perm.begin(), perm.end()));
    cout << ans << endl;
}

D - Calculating GCD

これ好き、平成ABC-Dっぽい感じ?
累積GCDを取った配列に対し、S_iごとににぶたんして上げる
 r = Nになったときだけ1にならないので、最後の要素とのGCDを出力、それ以外はr+1を出力
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, Q;
    cin >> N >> Q;
    vector<ll> A(N);
    REP(i,N){
        cin >> A[i];
        if(i > 0){
            A[i] = gcd(A[i-1], A[i]);
        }
    }

    REP(i,Q){
        ll S;
        cin >> S;
        int l = -1, r = N;
        while(r - l > 1){
            int m = (l + r) >> 1;
            if(gcd(A[m], S) > 1) l = m;
            else r = m;
        }
        if(r == N) cout << gcd(S, A[N-1]) << endl;
        else cout << r + 1 << endl;
    }
}

おわりに

unratedコンは気軽で楽しいね
緊張感あるratedも楽しいけど気兼ねなく楽しめるっていい