裏紙

ほぼ競プロ、たまに日記

SRM 672 Div1 Easy - Procrastination

問題

TopCoder Statistics - Problem Statement

問題概要

とある巨大な会社には従業員が無限にいる。従業員には1,2,3,...と番号が振られている。n番の従業員は仕事をし始める時はn番のタスクを請け負っている。その会社の就業時間は無限にあり、仕事時間が1時間ごとに区切られている。

その区切られた時間を1時間目、2時間目、...と呼ぶことにして、2以上のhに対して、h時間目に以下のことを行う:

hより大きく、hで割り切れる番号(つまり、 2h, \, 3h, \, ...番)の従業員は自分の番号より1大きい人とタスクを交換する。 これを無限に繰り返していく時、n番目のタスクを最終的に抱えている人は何番の従業員になるか求めよ。

  •  2 \le n \le 10^{10}

イデア

まず、この操作は無限に続いていくが、m番目の従業員がタスクを交換するタイミングは2h \le mであるタイミングでしか起き得ないので、有限時間で終わらせることができることが分かる。

更に、m番目の従業員がタスクを交換するタイミングはhmm-1の約数となるタイミングだけであるから、そんなに交換する回数はnに対して多くならないはずである。

nのサイズ的に、hを1つずつ増やしながらシミュレーションしていくと時間が足りないが、交換するタイミングだけに注目して計算できればそんなに約数の個数はどの数に対しても多くないので割と効率的に出来るのではないか、と考える。そうすると、m番目の従業員に対して、交換が起きるタイミングはmの約数とm-1の約数の中でh以上のもののうち最小の値のタイミングで起きることが分かる。

mの約数をリストで返す関数を作る。これはO(\sqrt{m})で実現できる。また、図をかいてシミュレーションしてみると同じような数に対して何度もこのアクセスが起こることが分かる。なのでmapで既に求めたものを保存しておけば、アクセス回数を減らすことが出来て効率的に計算できる。リストの中から、h以上のものの中で最小値を探す動作はlower_boundが良い。

いまいち計算量の見積はむずかしいが、約数の数はそんなにおおくないのでこれで十分間に合う。

実装(C++)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,n) for(i=0;i<n;++i)
#define each(itr,c) for(__typeof(c.begin()) itr=c.begin(); itr!=c.end(); ++itr)
#define mp make_pair
#define pb push_back
#define fi first
#define sc second

class Procrastination {
    public:
    //nの約数を返す
    vector<ll> factor(ll n){
      vector<ll> ret;
      ret.pb(1);
      for(ll i=2; i*i<=n; ++i){
        if(n%i==0) ret.pb(i);
      }

      int z=ret.size();
      for(int i=z-1; i>=0; --i) ret.pb(n/ret[i]);

      sort(ret.begin(),ret.end());
      ret.erase(unique(ret.begin(),ret.end()),ret.end());
      return ret;
    }

    long long findFinalAssignee(long long n) {
      if(n<=3) return n;

      //現在そのタスクを抱えている従業員
      ll ret=n;

      ll h=2;
      map<ll,vector<ll>> m;

      ll ct=0;
      while(h*2<=ret){
        vector<ll> now;
        vector<ll> pre;

        if(m.find(ret)==m.end()) now=m[ret]=factor(ret);
        else now=m[ret];

        if(m.find(ret-1)==m.end()) pre=m[ret-1]=factor(ret-1);
        else pre=m[ret-1];

        ll a = *lower_bound(now.begin(),now.end(),h);
        ll b = *lower_bound(pre.begin(),pre.end(),h);

        if(a<b){
          ++ret;
          h=a+1;
        }
        else{
          --ret;
          h=b+1;
        }

        //printf("ret= %lld, h= %lld\n",ret, h);
        //printf("a,b :%lld %lld\n",a,b);
      }

      return ret;
    }
};