秘密の本棚

気になることをなんでも書きます

AtCoder Beginner Contest 107 - Median of Medians

AtCoder Beginner Contest 107 - AtCoder

A - Train

問題

A - Train

考察

一列に並んだN個のものがあり、左から数えてi番目のものは右から数えて何番目か。だいたいN-iだが、テストケースからN-i+1だとわかる(雑)。

コード

int main(){
  ll n,i;
  cin >> n >> i;
  cout << n-i+1 << endl;
  return 0;
}

B - Grid Compression

問題

B - Grid Compression

考察

「.」または「#」が格子状に並んでおり、「.」のみからなる列や行を削除して表示する。どの列・行が「.」のみからなるかを調べ、それらを除いて表示すればよい。

コード

int main(){
  ll h,w;
  cin >> h >> w;
  char a[h][w];
  REP(i,h)REP(j,w) cin >> a[i][j];
  bool blank_row[h], blank_col[w];
  fill(blank_col,blank_col+w,true);
  fill(blank_row,blank_row+h,true);
  REP(i,h)REP(j,w){
    if(a[i][j]=='#') blank_row[i] = false;
  }
  REP(j,w)REP(i,h){
    if(a[i][j]=='#') blank_col[j] = false;
  }
  REP(i,h){
    REP(j,w){
      if(!blank_row[i]&&!blank_col[j]) cout << a[i][j];
    }
    if(!blank_row[i]) cout << endl;
  }
  return 0;
}

C - Candles

問題

C - Candles

考察

数直線上に並んだN個の点列のうちk個を廻り、その後原点に達する経路の最短経路の距離を求める。k個の点を廻る間に向きを変えるのは無駄なので、この間は同一方向に進み、この距離はiを座標が小さい方の番号として x _ {i+k-1}-x _ {i}である。iを固定したとき、左右どちらの頂点から原点に達するかを考えて答えは

{\displaystyle \min(x _ {i+k-1}-x _ i+|x _ i|,x _ {i+k-1}-x _ i+|x _ {i+k-1}|)}

で、さらにiについても動かして、求める値は

{\displaystyle
\min\{\min(x _ {i+k-1}-x _ i+|x _ i|,x _ {i+k-1}-x _ i+|x _ {i+k-1}|), i=1,2,\cdots,n-k+1\}}
である。

コード

int main(){
  ll n,k;
  cin >> n >> k;
  ll x[n];
  REP(i,n) cin >> x[i];
  ll ans = INF;
  REP(i,n-k+1){
    ans = min(ans,abs(x[i]-x[i+k-1])+abs(x[i]));
  }
  REP(i,n-k+1){
    ans = min(ans,abs(x[i]-x[i+k-1])+abs(x[i+k-1]));
  }
  cout << ans << endl;
  return 0;
}

D - Median of Medians

問題

D - Median of Medians

考察

長さNの数列aが与えられ、すべての部分列a\left[l,r\right]の中央値によって新たな数列を作るとき、その数列の中央値を求める。最終的な答えは元の数列aの要素であるので、aをソートしてから二分探索で答えを求めることを考える。ソート済みの数列ax番目(Xとする)が求める中央値よりも大きいかどうかは、以下のように判定できる。

いま、ある部分列a\left[l,r\right]の中央値がX以上であるということは、その部分列の中に含まれるXよりも小さい要素の個数が、X以上である要素の個数以下であることと同値である。さらに言い換えると、数列aの各要素がXよりも小さければ-1,X以上であれば1とした数列bを考えて、区間\left[l,r\right]の中にb[i]=-1なるiの個数がb[i]=1なるiの個数以下であることと言いかえられる。

これをそのまま解くのは難しいので、累積和を用いる。b[i]の累積和をs[i]とおくと(ただしs[0]=0とする)、上の条件はs[r]-s[l-1]\geq0と言いかえられる。これをみたす(l,r)の組の個数を求めるのはBIT(Binary Indexed Tree)を用いると実現できる。

いまsの値の範囲は-n以上n以下であることに注意し、2n+1個のBITを考える。蟻本162ページを参考にして個数を求めるが、sの要素数n+1個であることに注意が必要。中央値からなる数列の個数は、l=rの場合とl\lt rの場合を分けて足すことにより

\displaystyle n+\frac{n(n-1)}{2}=\frac{n(n+1)}{2}
なので、この半分と求めた個数を比較する。

コード(本問)

int main(){
  ll n;
  cin >> n;
  ll a[n];
  ll a_sort[n];
  REP(i,n){
    cin >> a[i];
    a_sort[i] = a[i];
  }
  sort(a_sort,a_sort+n);
  ll lb = 0, ub = n;
  while(ub-lb>1){
    ll x = (lb + ub)/2;
    ll b[n];
    REP(i,n) b[i] = (a[i]<a_sort[x]) ? -1 : 1;
    ll s[n];
    REP(i,n){
      s[i] = (i==0) ? b[i] : s[i-1]+b[i];
    }
    ll cnt = 0;
    BIT bit(2*n+1);//値の範囲は-n<=x<=n
    bit.add(n,1);
    REP(j,n){
      cnt += bit.sum(s[j]+n);
      bit.add(s[j]+n,1);
    }
    if(cnt>=ceil(n*(n+1)/2/2)){
      lb = x;
    }else{
      ub = x;
    }
  }
  cout << a_sort[lb] << endl;
  return 0;
}

コード(BIT部分)

struct BIT{
  ll M;
  std::vector<ll> bit;
  BIT(ll n){
    init(n);
  }
  void init(ll n){
    M = n;
    bit.resize(M+1);
    fill(bit.begin(),bit.end(),0);
  }
  ll sum(ll i){
    ll s = 0;
    while(i>0){
      s += bit[i];
      i -= i & -i;
    }
    return s;
  }
  void add(ll i, ll x){
    while(i<=M){
      bit[i] += x;
      i += i & -i;
    }
  }
};

雑感

完全にDはお手上げ状態でした。蟻本だと中級編の総合的な理解が必要とされるレベルで、さすがに厳しかったです。Cまでを14分で解き切って、その後はアイスを食べたりしていました(笑)。 f:id:nexusuica:20180826130914p:plain