AISing Programming Contest 2019:D - Nearest Card Game
結論
A の index は 0 始まりとします。また、以下で「左」や「右」と書いているのは、カードを から まで左から右へと並べた状況を想定しています。
N が偶数のとき
を以下で定義します
このとき を満たす p が 1 つ存在し、 高橋君が右から p 枚、青木君が続く p 枚を取り、残りは高橋君から始めて右から交互に取っていきます。
は減少列なので、このような p は二分探索で で求められます。
また、高橋君が取ったカードの和は、累積和 と、indexが偶数のみの和 をあらかじめ計算しておくと、
で求められます。
クエリは 個なので、累積和の計算もあわせて計算量は となります。
N が奇数のとき
を以下で定義します
を満たす p が存在するとき、カードの取られ方は上記の N が偶数の場合と同様で、求める和は
です。
存在しないとき、高橋君が右の (N+1)/2 枚、青木君が左の (N-1)/2 枚を取ります。求める和は
となります。
ごちゃごちゃした説明
カードの取られ方
まずはカードがどのように取られていくかを把握したいです。
パターン(1)
高橋君は大きな値から順に、青木君は に近い値から順番に取っていくので、ゲーム開始からしばらくすると、高橋君がカードの右から p 枚を、青木君が続く p 枚を、それぞれ取った状況になります。その後は高橋君と青木君が交互に残ったカードの一番右を取っていきます。ここで、 p = 1 とすると下記のパターン(2)と一致してしまうので、ここでは p > 1 とします。
パターン(2)
が十分に大きな値のとき、高橋君と青木君は、右から順番に交互にカードを取っていくことになります。
パターン(3)
がもっと小さくなった場合、高橋君がカードの右から 枚を、青木君が左から 枚 を取ります。 が偶数のときはこれで終了、 が奇数の場合は残ったカード を高橋君が取って終了となります。
パターン(1)の条件
パターン(1)となるのがどういう場合かを考察し、それが成り立たない場合として(2),(3)の場合を考えることにします。
まずは高橋君がカードの右から p 枚を、青木君が続く p 枚を、それぞれ取った状況(上図)です。 p > 1 に注意して下さい。
(i) 青木君が p 枚目に を取ったとき
このとき、青木君は x に最も近い p 枚のカードを取っており、その p 番目のカードとして を取っています。なぜなら、 より右や より左にもっと x に 近いカードがあれば、p - 1枚目までにそちらを先に取っているはずだからです。
・ が よりも先に取られていることより、 。
・ p 枚目に ではなく を取ったことから、。
2つ合わせて、 となります。
ただし、N が偶数で p が N/2 の場合、上記の2つ目の条件は不要です。なぜなら、 青木君が p 枚目のカードとして を取る時点で、残ったカードは しかないからです。よって、以下のように書けます。
(ii) 青木君が p 枚目に を取ったとき
2 つの場合が考えられます。
(ii - 1) x に p 番目に近いカードとして を取った場合
・ が よりも先に取られていることより、 。
・ が よりも x に近いことより、。
二つ合わせて、
(ii - 2) p 番目に x に近いカード を高橋君にとられ、 を取った場合
・ が よりも先に取られていることより、 。
・ が よりも x に近いことより、。
・ を よりも先に取ったことより、。
以上3つを合わせて、以下のように書けます:
よって、高橋君と青木君が合わせて 2p 枚取った時点で、それらがカードの右 2p 枚であるための条件は、上記の (i) または (ii-1) または (ii-2) より、
と定義して、
と書けます。
パターン(2)の条件
青木君が最初に取るカードが となる条件と同じなので、 が得られます。
従って、 として の場合、と言い換えられます。
パターン(3)の条件
N が偶数の場合、パターン(1)でp=N/2とした場合に一致します。
N が奇数の場合、 は x に p + 1 番目に近い値となるので、x は と の平均値以下です。よって、 としたとき と書けます。これは「(1)の条件を満たす p が存在しない場合」と言い換えられます。
ソースコード
#include <bits/stdc++.h> using namespace std; typedef long long ll; int main(){ int N, Q; cin >> N >> Q; vector<ll> A(N), X(Q); for(int i=0; i<N; i++) cin >> A[i]; for(int i=0; i<Q; i++) cin >> X[i]; vector<ll> S(N), T(N); S[0] = A[0]; T[0] = A[0]; for(int i=1; i<N; i++){ S[i] = S[i-1] + A[i]; T[i] = T[i-1]; if(i%2==0) T[i]+=A[i]; } int P = N/2+1; vector<ll> B(P+1); // B の値をあらかじめ計算しておく B[1] = 1e18; if(N%2==1) B[P] = A[N-P] + A[N-2*P+1]; else B[P] = 0; for(int p=2; p<P; p++){ B[p] = A[N-p] + A[N-2*p+1]; } for(int i=0; i<Q; i++){ ll x = X[i]; if(2*x <= B[P]){ //不等式を満たす p が存在しないとき cout << S[N-1] - S[P-2] << endl; }else{ int l = 1, r = P-1; int p = l + (r-l)/2; while(!(B[p+1] < 2*x && 2*x <= B[p])){ // 不等式を満たす p を二分探索 if(B[p+1] >= 2*x) l = p + 1; else r = p; p = l + (r-l)/2; } if(N%2==0){ cout << S[N-1] - S[N-p-1] + S[N-2*p-1] - T[N-2*p-1] << endl; }else{ cout << S[N-1] - S[N-p-1] + T[N-2*p-1] << endl; } } } }