競プロ参加日記011 Codeforces Round #669 (Div. 2)

ぬるからです。
3回目のcodeforces参加となりました。
https://codeforces.com/contest/1407

・はじめに

A,Bの2完で2982位でした。
レートは+251で1262へ。何とか色が付きました。

・A. Ahahahahahahahaha

1と0で構成される数列からn/2個まで要素を削除し、奇数番目の要素の和-偶数番目の要素の和が0になるようにする。
1と0の2パターンしかないため、どちらか(または両方)の個数がn/2個以下になるはずです。つまり、数が少ないほうの要素を消せば全て1または、全て0にできます。
全て0にできる場合、和は0になるため題意を満たします。
全て1にできる場合、要素の個数が偶数個であれば、奇数番目の和と偶数番目の和が等しくなるため0になります。(0=n/2個,1=n/2個のパターンを0残しにするようにすると、1が奇数個であっても必ず1つは消せる操作が残っているはずです。なので、奇数個の場合は1を余分に1つ消し、偶数個にすれば問題ありません。)
上記の考えで実装していきます。

//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<climits>
/*
#include<boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/rational.hpp>
namespace mp = boost::multiprecision;
using Real = mp::number<mp::cpp_dec_float<1024>>;
using Bint = mp::cpp_int;
*/
using namespace std;
#define REP(i,s,e) for((i)=(s);(i)<(e);(i)++)
#define RREP(i,s,e) for((i)=((s)-1);(i)>=(e);(i)--)
#define FOR(i,n) for((i)=(0);(i)<(n);(i)++)
#define RFOR(i,n) for((i)=((n)-1);(i)>=(0);(i)--)
#define MOD 1000000007
//0001010110
int main(){
   int t;
   cin>>t;
   for(int i=0;i<t;i++){
       int n;
       cin>>n;
       int a;
       int o=0,z=0;
       for(int j=0;j<n;j++){
           cin>>a;
           if(a==0) z++;
           else o++;
       }
       if(z>=n/2){
           cout<<n/2<<endl;
           for(int j=0;j<n/2;j++){
               cout<<"0";
               if(j!=n/2-1) cout<<" ";
               else cout<<endl;
           }
       }
       else{
           int n2=n/2;
           if(n/2%2==1) n2++;
           cout<<n2<<endl;
           for(int j=0;j<n2;j++){
               cout<<"1";
               if(j!=n2-1) cout<<" ";
               else cout<<endl;
           }
       }
   }
}

・B. Big Vova

a[]を並び替えてb[]を作る。この時、ciの各要素はb0~biのgcdになる。c[]が辞書順で最も大きくなるようなb[]を求めよ。
b0=c0になるので、b0はa[]のmaxを入れるのが適切です。残りのb1~ですが、この問題はn<=10^3と制約が弱く、gcdが最大になるものを全探索で求めていっても余裕で間に合います。

//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<climits>
/*
#include<boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/rational.hpp>
namespace mp = boost::multiprecision;
using Real = mp::number<mp::cpp_dec_float<1024>>;
using Bint = mp::cpp_int;
*/
using namespace std;
#define REP(i,s,e) for((i)=(s);(i)<(e);(i)++)
#define RREP(i,s,e) for((i)=((s)-1);(i)>=(e);(i)--)
#define FOR(i,n) for((i)=(0);(i)<(n);(i)++)
#define RFOR(i,n) for((i)=((n)-1);(i)>=(0);(i)--)
#define MOD 1000000007
//0001010110
int gcd(int a,int b){
   if(b>a) return gcd(b,a);
   if(b==0) return a;
   return gcd(b,a%b);
}
int main(){
   int t;
   cin>>t;
   for(int i=0;i<t;i++){
       int n;
       cin>>n;
       int a[n];
       for(int j=0;j<n;j++){
           cin>>a[j];
       }
       sort(a,a+n);
       int gd=a[n-1];
       cout<<a[n-1]<<" ";
       a[n-1]=-1;
       for(int j=0;j<n-1;j++){
           int mxi=-1;
           int mx=-1;
           for(int k=0;k<n;k++){
               if(a[k]!=-1){
                   if(mx==-1){
                       mx=gcd(gd,a[k]);
                       mxi=k;
                   }
                   else{
                       int g=gcd(gd,a[k]);
                       if(g>mx){
                           mx=g;
                           mxi=k;
                       }
                   }
               }
           }
           cout<<a[mxi];
           if(j==n-1-1) cout<<endl;
           else cout<<" ";
           gd=gcd(gd,a[mxi]);
           a[mxi]=-1;
       }
   }
}

・C. Chocolate Bunny

珍しい形式の問題です。Atcoderの過去問とかをやっていると、たまに見かけます。
長さnの数列p[]があります。? x yと出力することで、p[x]%p[y]を知ることができるため、2n回までの質問で数列を当ててください。
modについて少し考えると答えが出ます。
a<bのとき、
a%b=a
b%a<a (0~a-1)
になります。この時、答えの大きいほう(a%b)は必ず答えがaになり、l答えの小さいほう(b%a)は必ず答えがaになりません。つまり、a%b,b%aと2回質問を送ることでaかbが確定できます。
ex.a=6 b=13の場合
a%b=6
b%a=1
答えの大きいほうである、a%b=6よりa=6が確定する。
この操作は2クエリで1つ確定できるため、単純計算で2nのクエリで答えが求まります。問題の制約的にも大丈夫そうです。

//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<climits>
/*
#include<boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/rational.hpp>
namespace mp = boost::multiprecision;
using Real = mp::number<mp::cpp_dec_float<1024>>;
using Bint = mp::cpp_int;
*/
using namespace std;
#define REP(i,s,e) for((i)=(s);(i)<(e);(i)++)
#define RREP(i,s,e) for((i)=((s)-1);(i)>=(e);(i)--)
#define FOR(i,n) for((i)=(0);(i)<(n);(i)++)
#define RFOR(i,n) for((i)=((n)-1);(i)>=(0);(i)--)
#define MOD 1000000007
//0001010110
int ans[10001];
int n;
void dfs(vector<int> v){
   if(v.size()==1){
       ans[v[0]]=n;
       return;
   }
   vector<int> vmx;
   int a,b;
   for(int i=0;i<v.size();i+=2){
       if(i+1>=v.size()){
           vmx.push_back(v[i]);
           break;
       }
       cout<<"? "<<v[i]<<" "<<v[i+1]<<endl;
       cin>>a;
       cout<<"? "<<v[i+1]<<" "<<v[i]<<endl;
       cin>>b;
       if(a>b){
           vmx.push_back(v[i+1]);
           ans[v[i]]=a;
       }
       else{
           vmx.push_back(v[i]);
           ans[v[i+1]]=b;
       }
   }
   dfs(vmx);
}
int main(){
   cin>>n;
   vector<int> v;
   for(int i=0;i<n;i++){
       v.push_back(i+1);
   }
   dfs(v);
   cout<<"! ";
   for(int i=1;i<=n;i++){
       cout<<ans[i];
       if(i==n) cout<<endl;
       else cout<<" ";
   }
}

※codeforcesは数学系の問題が少ないと聞いていたのですが、出ないってわけでは無いんですね...。
 こんなの絶対コンテスト中に分かりません。

・D. Discrete Centrifugal Jumps

※完全、後から思い出すための自分用のメモです。
 多分、他の方が見ても訳わからないことが書いてるかと思います。(うまく、纏められませんでした...)

3つの条件
・移動先が1つ先
・移動前と移動先より、間にある建物が全て大きい
・移動前と移動先より、間にある建物が全て小さい
のどれかを満たすとき移動できる。1->nに移動する最短手順は幾つか。

移動できる経路に辺を張り、BFSで最短経路を求めたいですが、全hiに関して調べているとO(n^2)かかってだめです。
移動できる条件を少し考えてみます。
x 8 5となった場合、xが7,6,5,4,3などの場合はx->5に移動できます。
x 7 8 5 となった場合、xが6,5,4,3などの場合はx->5に移動できます。
x 4 7 8 5 となった場合、xに何を入れてもx->5には移動できません。また、これより左は値に関係なく->5には移動できなさそうです。
つまり、移動先pj<pj-1のとき、インデックスを1減らしていってpj以下になるpj-nまでが移動元の可能性になりそうです。(※9 7 8 5みたいに、増えるケースは移動できません。ただ、6 9 7 8 5のように、左の値によっては->5へ移動できる可能性はあります。)
pj>pj-1の場合は、同様に考えてpj以上になるpj-nまで移動が可能です。
纏めると、
pj<pj-1のとき、i=pj-1として、pi未満の値の中で最も近いものから移動できる。また、その箇所をiとしてpiがpj以下になるまで再帰的に移動元が算出できる。
pj>pj-1のときも同じようなことがいえます。

各piに対して、左の区間の一番近いpi未満のindexとpiより大きいindexを保持できれば、有向グラフが作れそうです。
"左の区間の一番近い"は言い換えると。区間(0,i-1)の一番右の値と言えます。こういう操作はセグ木の二分探索で行えます。

纏めると、この問題は以下のステップで解けます。
・入力からminとmaxの二つのセグ木を構築
・セグ木の二分探索で、各piの区間(0,i-1)の一番右にあるpiより大きい値の場所と、pi未満の値の場所を記録
・記録した値をもとに、移動できる場所を算出。有向グラフを作成。
・有向グラフに対してBFSで最短経路を算出。
以下、コードです。

//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<climits>

/*
#include<boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/rational.hpp>
namespace mp = boost::multiprecision;
using Real = mp::number<mp::cpp_dec_float<1024>>;
using Bint = mp::cpp_int;
*/

using namespace std;

#define REP(i,s,e) for((i)=(s);(i)<(e);(i)++)
#define RREP(i,s,e) for((i)=((s)-1);(i)>=(e);(i)--)
#define FOR(i,n) for((i)=(0);(i)<(n);(i)++)
#define RFOR(i,n) for((i)=((n)-1);(i)>=(0);(i)--)
#define MOD 1000000007
//0001010110

//0-index
vector<int> seqv,seqv2;
int seqv_n;
void seg_init(int N,int a){
  seqv.clear();
  seqv2.clear();
  int n=1;
  while(n<N) n*=2;
  seqv_n=n-1;
  n*=2;
  n=n-1;
  for(int i=0;i<n;i++){
      seqv.push_back(a);
      seqv2.push_back(a);
  }
}
void seg_update2(int n,int a){
  int k=n+seqv_n;
  seqv2[k]=a;
  while(k>0){
      k=(k-1)/2;
      seqv2[k]=max(seqv2[k*2+1],seqv2[k*2+2]);
  }
}

int seg_find2(int x,int y){
  struct segF{
      static int segfind(int x,int y,int k,int l,int r){
          //cout<<x<<","<<y<<","<<k<<","<<l<<","<<r<<endl;
          if(r<x||l>y) return INT_MIN;
          if(l>=x&&r<=y) return seqv2[k];
          else{
              int segl=segfind(x,y,k*2+1,l,(l+r)/2);
              int segr=segfind(x,y,k*2+2,(l+r)/2+1,r);
              return max(segl,segr);
          }
      }
  };
  return segF::segfind(x,y,0,0,seqv_n);
}
void seg_update(int n,int a){
  int k=n+seqv_n;
  seqv[k]=a;
  while(k>0){
      k=(k-1)/2;
      seqv[k]=min(seqv[k*2+1],seqv[k*2+2]);
  }
}

int seg_find(int x,int y){
  struct segF{
      static int segfind(int x,int y,int k,int l,int r){
          //cout<<x<<","<<y<<","<<k<<","<<l<<","<<r<<endl;
          if(r<x||l>y) return INT_MAX;
          if(l>=x&&r<=y) return seqv[k];
          else{
              int segl=segfind(x,y,k*2+1,l,(l+r)/2);
              int segr=segfind(x,y,k*2+2,(l+r)/2+1,r);
              return min(segl,segr);
          }
      }
  };
  return segF::segfind(x,y,0,0,seqv_n);
}

int seg_rfind(int x,int y,int a){
   struct segF{
       static int segfind(int x,int y,int a,int k,int l,int r){
           //cout<<x<<","<<y<<","<<k<<","<<l<<","<<r<<endl;
           if(seqv[k]>a||r<x||l>y) return INT_MAX;
           if(k>=seqv_n) return k-seqv_n;
           int sgr=segfind(x,y,a,2*k+2,(l+r)/2+1,r);
           if (sgr!=INT_MAX) {
               return sgr;
           } else {
               return segfind(x,y,a,2*k+1,l,(l+r)/2);
           }
       }
   };
   return segF::segfind(x,y,a,0,0,seqv_n);
}
int seg_rfind2(int x,int y,int a){
   struct segF{
       static int segfind(int x,int y,int a,int k,int l,int r){
           //cout<<x<<","<<y<<","<<k<<","<<l<<","<<r<<endl;
           if(seqv2[k]<a||r<x||l>y) return INT_MIN;
           if(k>=seqv_n) return k-seqv_n;
           int sgr=segfind(x,y,a,2*k+2,(l+r)/2+1,r);
           if (sgr!=INT_MIN) {
               return sgr;
           } else {
               return segfind(x,y,a,2*k+1,l,(l+r)/2);
           }
       }
   };
   return segF::segfind(x,y,a,0,0,seqv_n);
}

int main(){
   int n;
   cin>>n;
   seg_init(n,0);
   int a[n];
   for(int i=0;i<n;i++){
       cin>>a[i];
       seg_update(i,a[i]);
       seg_update2(i,a[i]);
   }
   vector<int> v[n];
   int nmx[n],nmi[n];

   for(int i=n-1;i>=0;i--){
       int nn=seg_find(i,i);
       int mx=seg_rfind2(0,i-1,nn+1);
       int mi=seg_rfind(0,i-1,nn-1);
       nmx[i]=mx;
       nmi[i]=mi;
       //cout<<i<<","<<mx<<","<<mi<<endl;
   }

   for(int i=1;i<n;i++){
       int f=i-1;
       v[f].push_back(i);
       if(a[i]<a[i-1]){
           while(1){
               if(nmi[f]==INT_MAX) break;
               f=nmi[f];
               v[f].push_back(i);
               if(a[i]>=a[f]) break;
           }
       }
       else if(a[i]>a[i-1]){
           while(1){
               if(nmx[f]==INT_MIN) break;
               f=nmx[f];
               v[f].push_back(i);
               if(a[i]<=a[f]) break;
           }
       }
   }

   int dp[n];
   for(int i=0;i<n;i++) dp[i]=-1;

   queue<int> q;
   q.push(0);
   dp[0]=0;
   while(!q.empty()){
       int qn=q.front();
       q.pop();
       for(int i=0;i<v[qn].size();i++){
           int tn=v[qn][i];
           if(dp[tn]==-1||dp[tn]>dp[qn]+1){
               dp[tn]=dp[qn]+1;
               q.push(tn);
           }
       }
   }
   cout<<dp[n-1]<<endl;
}

※セグ木の操作関数が多いですが、min用のセグ木とmax用のセグ木で分けているためです。
 こういうの、抽象化でセグ木を作ればきれいに書けるっぽいのですが、ぬるからのセグ木は未対応です。

・終わりに

コンテスト後の解きなおしですが、
始めて整備したセグ木で、参加したコンテストの問題が解けました。
ちょっと、自分が分かりやすいように参考にしたコードから微妙に変えているので、ちゃんと動いているっぽくて良かったです。

この記事が気に入ったらサポートをしてみませんか?