問題一覧 > スコア問題

No.5012 Better Mo's Algorithm is Needed!! (Execute)

レベル : / 実行時間制限 : 1ケース 0.500秒 / メモリ制限 : 512 MB / スペシャルジャッジ問題 (複数の解が存在する可能性があります)
タグ : / 解いたユーザー数 7
作問者 : butsurizuki
0 ProblemId : 8926 / 自分の提出
問題文最終更新日: 2022-12-13 21:01:08

おことわり

この問題は、それなりに競技性に欠くスコア問題である可能性があります。どうか、あまりムキにならないでください。
同一の 出力 / 提出 に対してスコアが変動しますが、ジャッジがそこそこ重いため過度な同一提出はお控えください。

問題文

実行時間制限が通常より短い 500500 ms であることに注意してください。
また、非常に特殊な採点方法に注意してください。
スコア問題であるため、最後の提出から5分間は次の提出を行うことができません。

ここに、以下のような問題があります。この問題は Static Range Inversions Query としてよく知られています。

数列 A=(A1,A2,,AN)A=(A_1,A_2,\dots,A_N) が与えられます。
以下の QQ 個の問い合わせに答えてください:
  • ii 番目の問い合わせでは li,ril_i,r_i が与えられる。連続部分列 (Ali,Ali+1,,Ari)(A_{l_i},A_{l_i+1},\dots,A_{r_i}) の転倒数を求めよ。
制約 ・入力は全て整数 ・1N,Q1051 \le N,Q \le 10^50Ai1090 \le A_i \le 10^91liriN1 \le l_i \le r_i \le N 実行時間制限: 22 s

物理好きさんは (1,2,,Q)(1,2,\dots,Q) の順列である PP さえうまく定めれば先程の問題に正解する 以下のコード CC を書きました。

#include <bits/stdc++.h>
#include <sys/time.h>

using namespace std;

long long start_time;

void start_clock(){
  struct timeval tv;
  gettimeofday(&tv, NULL);
  start_time=(tv.tv_sec*1000000+tv.tv_usec);
}

long long current_clock(){
  struct timeval tv;
  gettimeofday(&tv, NULL);
  long long current_time=(tv.tv_sec*1000000+tv.tv_usec);
  // cout << current_time-start_time << "(us)\n";
  return current_time-start_time;
}

int bit[111111]={0};
int bsize;

int BIT_sum(int i){
  int s=0;
  while(i>0){
    s+=bit[i];
    i-=i&(-i);
  }
  return s;
}

void BIT_add(int i,int x){
  while(i<=bsize){
    bit[i]+=x;
    i+=i&(-i);
  }
}

int N,Q;
int A[111111];
int l[111111],r[111111];
int P[111111];

int whole=0;
long long result=0;
void add(int i,bool isLeft){
  if(isLeft){
    result+=BIT_sum(A[i]-1);
  }
  else{
    result+=whole;
    result-=BIT_sum(A[i]);
  }
  BIT_add(A[i],1);
  whole++;
}

void rem(int i,bool isLeft){
  BIT_add(A[i],-1);
  whole--;
  if(isLeft){
    result-=BIT_sum(A[i]-1);
  }
  else{
    result-=whole;
    result+=BIT_sum(A[i]);
  }
}

int cur_l,cur_r;

long long mo_work(int l,int r){
  while(l < cur_l){cur_l--;add(cur_l,true);}
  while(cur_r < r){cur_r++;add(cur_r,false);}
  while(cur_l < l){rem(cur_l,true);cur_l++;}
  while(r < cur_r){rem(cur_r,false);cur_r--;}
  return result;
}

long long output[111111];

int main(){
  // 入力処理
  cin >> N >> Q;
  bsize=N;
  for(int i=1;i<=N;i++){
    cin >> A[i];
  }
  for(int i=1;i<=Q;i++){
    cin >> l[i] >> r[i];
  }

  // 座標圧縮
  map<int,int> mp;
  for(int i=1;i<=N;i++){
    mp[A[i]]=1;
  }
  int comp=1;
  for(auto &nx : mp){
    nx.second=comp;
    comp++;
  }
  for(int i=1;i<=N;i++){
    A[i]=mp[A[i]];
  }

  // 順列 P をうまく定める
  for(int i=1;i<=Q;i++){
    P[i] = ( 出力された P_i );
  }

  // 計時開始
  start_clock();
  for(int i=1;i<=Q;i++){
    if(i==1){
      cur_l=l[P[i]];
      cur_r=l[P[i]];
      add(l[P[i]],true);
    }
    output[P[i]]=mo_work(l[P[i]],r[P[i]]);

    if(i%100==0){
      // TLEノックアウト
      if(current_clock()>1500000){break;}
    }
  }

  // 計時終了
  long long exe_time=current_clock();

  // 出力
  for(int i=1;i<=Q;i++){
    cout << output[i] << "\n";
  }
  return 0;
}

コード CC の簡単な説明:
  • 入力を受け取る。
  • AA を座標圧縮する。
  • 出力された順列 PP を受け取る。
  • Mo's Algorithm と同じ要領で区間の転倒数を求めていく。
    • ii 番目には PiP_i 番目の問い合わせの区間に対して答えを求める。
    • 区間の 左端 / 右端 の 11 点 追加 / 削除 を行う際、 Binary Indexed Tree を利用して転倒数の変動を求める。
    • その際の計算量は O(logN)O(\log N) である。
  • Mo's Algorithm を ( PP のソートという形で ) うまく実装すれば O(NQlogN)O(N \sqrt{Q} \log N) という計算量にできる。

あなたの目的は、この順列 PP をうまく定めて物理好きさんのコードをなるべく高速に実行させることです。
但し、 PP を定めるのに使える時間は 500500 ms しかありません。
なぜなら、 Mo's Algorithm の本質部分に実行時間を 15001500 ms 残し、 22 s の実行時間制限に間に合わせたいからです。
あなたはより優秀な順列 PP を高速に見つけることができますか?

得点

この問題では、出力された順列 PP を利用して物理好きさんのコード CC を実際に実行した結果により得点が定められます。
この問題には 100 個のテストケースが含まれます。
コード CC が各テストケースに対して TT μs の実行時間を要したとします。
すると、各テストケースに対する得点は以下のように定められます。

  • T1.5×106T \le 1.5 \times 10^6 (実行時間が 15001500 ms 以下) である場合、得点は 1016+(1.5×106T)10^{16} + (1.5 \times 10^6 - T) 点である。
  • そうでない場合、得点は 00 点である。
各提出に対する最終的な得点は、全てのテストケースに対する得点の総和です。
なので、この得点は ( 15001500 ms 以内に AC したテストケース数) ×1016+\times 10^{16} + ( AC したテストケースについての残り実行時間の合計 (μs) ) と表現できます。
実行時間 TT はジャッジコード内の変数 exe_time の値により決定されます。
これは、順列 PP を定める実行時間制限 500500 ms のうちどれだけを消費したかには非依存です。

なお、 101810^{18} 点以上が獲得できる (すなわち、全てのテストケースについてコード CC が TLE しない) プログラムが存在することを保証します。

入力

NN QQ
A1A_1 A2A_2 \dots ANA_N
l1l_1 r1r_1
l2l_2 r2r_2
\vdots
lQl_Q rQr_Q

入力は Static Range Inversions Query の入力です。物理好きさんのコードにも同じものが与えられて実行されます。

出力

順列 PP を以下の形式で出力せよ。

P1P_1 P2P_2 \dots PQP_Q

入力生成方法

入力のうち 4848 ケースは以下の方法により生成されます。
N,Q{10000,20000,50000,100000},N,Q \in \{10000,20000,50000,100000\}, 区間生成法 ST{1,2,3}ST \in \{1,2,3\} を満たす整数組 (N,Q,ST)(N,Q,ST)4848 通り考えられますが、各整数組に対応するテストケースが 11 つずつ生成されます。
AiA_i0Ai1090 \le A_i \le 10^9 の範囲で独立一様ランダムに生成されます。

それに加えて、入力のうち 3030 ケースは以下の方法により生成されます。
N=100000,Q=100000N=100000,Q=100000 で固定です。
AiA_i の種類数 k=10,102,103,104,105k = 10,10^2,10^3,10^4,10^5 であるケースが区間生成法 ST=1,2,3ST = 1,2,3 について各 22 個ずつ、計 3030 ケースが生成されます。
Ai=(0,1,,k1)A_i=(0,1,\dots,k-1) から始め、後ろに 00 以上 k1k-1 以下の一様乱数を NkN-k 個付けたのち AA をシャッフル、その後 同一の値が同一になる / 異なる値が異なる ように各値を 00 以上 10910^9 以下の一様ランダムな整数に付け替えます。

それに加えて、入力のうち 11 ケースは以下で示される サンプル1 に一致します。

それに加えて、入力のうち 11 ケースは こちらで公開されています。 N=100000,Q=100000,ST=3N=100000,Q=100000,ST=3 として生成したケースです。
AiA_i0Ai1090 \le A_i \le 10^9 の範囲で独立一様ランダムに生成されています。

残りの 2020 ケースにについて、入力生成方法は秘匿されます。どのような悪意を持ったケースも与えられる可能性があります。
但し、ジェネレータ欄にジェネレータを貼っています。コンテスト終了後などに参照してください。

各区間生成法の詳細は以下の通りです。
方法1:

  • 区間は必ず以下の方法で生成される。
  • x=x= rnd\rm{rnd}(1,N),y=(1,N),y= rnd\rm{rnd}(1,N)(1,N) で生成する。
  • もし x>yx>y なら x,yx,y を交換する。
  • 生成される区間は [x,y][x,y] である。
方法2:
  • 区間は必ず以下の方法で生成される。
  • l=l= rnd\rm{rnd}(1,N)(1,N) で生成する。
  • x=x= rnd\rm{rnd}(1,Nl+1)(1,N-l+1) で生成し、 y=x+l1y=x+l-1 とする。
  • 生成される区間は [x,y][x,y] である。
方法3:
  • 区間は 方法1 と 方法2 のどちらかが等確率で区間ごとに独立に選択され、その方法で生成される。

サンプル

サンプル1
入力
5 4
1 0 3 2 2
1 4
2 3
2 5
1 5
出力
4 2 1 3

もし、この出力に対してコード CC20002000 μs ( =2= 2 ms ) で動作した場合、このケースに対する得点は 1016+149800010^{16}+1498000 点となります。
もし、この出力に対してコード CC16000001600000 μs ( =1600= 1600 ms ) で動作した場合やコードが TLE ノックアウトした場合は、このケースに対する得点は 00 点となります。
ちなみに、本来の問題の正答は以下の通りです。

2
0
2
3

提出するには、Twitter 、GitHub、 Googleもしくは右上の雲マークをクリックしてアカウントを作成してください。