No.5012 Better Mo's Algorithm is Needed!! (Execute)
タグ : / 解いたユーザー数 7
作問者 : butsurizuki
おことわり
この問題は、それなりに競技性に欠くスコア問題である可能性があります。どうか、あまりムキにならないでください。
同一の 出力 / 提出 に対してスコアが変動しますが、ジャッジがそこそこ重いため過度な同一提出はお控えください。
問題文
実行時間制限が通常より短い $500$ ms であることに注意してください。
また、非常に特殊な採点方法に注意してください。
スコア問題であるため、最後の提出から5分間は次の提出を行うことができません。
ここに、以下のような問題があります。この問題は Static Range Inversions Query としてよく知られています。
数列 $A=(A_1,A_2,\dots,A_N)$ が与えられます。 以下の $Q$ 個の問い合わせに答えてください:
- $i$ 番目の問い合わせでは $l_i,r_i$ が与えられる。連続部分列 $(A_{l_i},A_{l_i+1},\dots,A_{r_i})$ の転倒数を求めよ。
物理好きさんは $(1,2,\dots,Q)$ の順列である $P$ さえうまく定めれば先程の問題に正解する 以下のコード $C$ を書きました。
#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; }
コード $C$ の簡単な説明:
- 入力を受け取る。
- $A$ を座標圧縮する。
- 出力された順列 $P$ を受け取る。
- Mo's Algorithm と同じ要領で区間の転倒数を求めていく。
- $i$ 番目には $P_i$ 番目の問い合わせの区間に対して答えを求める。
- 区間の 左端 / 右端 の $1$ 点 追加 / 削除 を行う際、 Binary Indexed Tree を利用して転倒数の変動を求める。
- その際の計算量は $O(\log N)$ である。
- Mo's Algorithm を ( $P$ のソートという形で ) うまく実装すれば $O(N \sqrt{Q} \log N)$ という計算量にできる。
あなたの目的は、この順列 $P$ をうまく定めて物理好きさんのコードをなるべく高速に実行させることです。
但し、 $P$ を定めるのに使える時間は $500$ ms しかありません。
なぜなら、 Mo's Algorithm の本質部分に実行時間を $1500$ ms 残し、 $2$ s の実行時間制限に間に合わせたいからです。
あなたはより優秀な順列 $P$ を高速に見つけることができますか?
得点
この問題では、出力された順列 $P$ を利用して物理好きさんのコード $C$ を実際に実行した結果により得点が定められます。
この問題には 100 個のテストケースが含まれます。
コード $C$ が各テストケースに対して $T$ μs の実行時間を要したとします。
すると、各テストケースに対する得点は以下のように定められます。
- $T \le 1.5 \times 10^6$ (実行時間が $1500$ ms 以下) である場合、得点は $10^{16} + (1.5 \times 10^6 - T)$ 点である。
- そうでない場合、得点は $0$ 点である。
なので、この得点は ( $1500$ ms 以内に AC したテストケース数) $\times 10^{16} + $ ( AC したテストケースについての残り実行時間の合計 (μs) ) と表現できます。
実行時間 $T$ はジャッジコード内の変数
exe_time
の値により決定されます。これは、順列 $P$ を定める実行時間制限 $500$ ms のうちどれだけを消費したかには非依存です。
なお、 $10^{18}$ 点以上が獲得できる (すなわち、全てのテストケースについてコード $C$ が TLE しない) プログラムが存在することを保証します。
入力
$N$ $Q$ $A_1$ $A_2$ $\dots$ $A_N$ $l_1$ $r_1$ $l_2$ $r_2$ $\vdots$ $l_Q$ $r_Q$
入力は Static Range Inversions Query の入力です。物理好きさんのコードにも同じものが与えられて実行されます。
出力
順列 $P$ を以下の形式で出力せよ。
$P_1$ $P_2$ $\dots$ $P_Q$
入力生成方法
入力のうち $48$ ケースは以下の方法により生成されます。
$N,Q \in \{10000,20000,50000,100000\},$ 区間生成法 $ST \in \{1,2,3\}$ を満たす整数組 $(N,Q,ST)$ は $48$ 通り考えられますが、各整数組に対応するテストケースが $1$ つずつ生成されます。
$A_i$ は $0 \le A_i \le 10^9$ の範囲で独立一様ランダムに生成されます。
それに加えて、入力のうち $30$ ケースは以下の方法により生成されます。
$N=100000,Q=100000$ で固定です。
$A_i$ の種類数 $k = 10,10^2,10^3,10^4,10^5$ であるケースが区間生成法 $ST = 1,2,3$ について各 $2$ 個ずつ、計 $30$ ケースが生成されます。
$A_i=(0,1,\dots,k-1)$ から始め、後ろに $0$ 以上 $k-1$ 以下の一様乱数を $N-k$ 個付けたのち $A$ をシャッフル、その後 同一の値が同一になる / 異なる値が異なる ように各値を $0$ 以上 $10^9$ 以下の一様ランダムな整数に付け替えます。
それに加えて、入力のうち $1$ ケースは以下で示される サンプル1 に一致します。
それに加えて、入力のうち $1$ ケースは こちらで公開されています。 $N=100000,Q=100000,ST=3$ として生成したケースです。
$A_i$ は $0 \le A_i \le 10^9$ の範囲で独立一様ランダムに生成されています。
残りの $20$ ケースにについて、入力生成方法は秘匿されます。どのような悪意を持ったケースも与えられる可能性があります。
但し、ジェネレータ欄にジェネレータを貼っています。コンテスト終了後などに参照してください。
各区間生成法の詳細は以下の通りです。
方法1:
- 区間は必ず以下の方法で生成される。
- $x=$ $\rm{rnd}$$(1,N),y=$ $\rm{rnd}$$(1,N)$ で生成する。
- もし $x>y$ なら $x,y$ を交換する。
- 生成される区間は $[x,y]$ である。
- 区間は必ず以下の方法で生成される。
- $l=$ $\rm{rnd}$$(1,N)$ で生成する。
- $x=$ $\rm{rnd}$$(1,N-l+1)$ で生成し、 $y=x+l-1$ とする。
- 生成される区間は $[x,y]$ である。
- 区間は 方法1 と 方法2 のどちらかが等確率で区間ごとに独立に選択され、その方法で生成される。
サンプル
サンプル1
入力
5 4 1 0 3 2 2 1 4 2 3 2 5 1 5
出力
4 2 1 3
もし、この出力に対してコード $C$ が $2000$ μs ( $= 2$ ms ) で動作した場合、このケースに対する得点は $10^{16}+1498000$ 点となります。
もし、この出力に対してコード $C$ が $1600000$ μs ( $= 1600$ ms ) で動作した場合やコードが TLE ノックアウトした場合は、このケースに対する得点は $0$ 点となります。
ちなみに、本来の問題の正答は以下の通りです。
2 0 2 3
提出するには、Twitter 、GitHub、 Googleもしくは右上の雲マークをクリックしてアカウントを作成してください。