ARC043 B - 難易度

やり直し

B: 難易度 - AtCoder Regular Contest 043 | AtCoder

問題概要

  • 難易度Diの問題がN個ある
  • 以下の条件を満たす4問を選ぶ選び方は何通りか
    • 2 問目の難易度は 1 問目の難易度の 2 倍以上である
    • 3 問目の難易度は 2 問目の難易度の 2 倍以上である
    • 4 問目の難易度は 3 問目の難易度の 2 倍以上である

部分点

4≦N≦3000

満点

4≦N≦105

解法

選ぶ問題を難易度順にa,b,c,dとする。

  • 部分点解法

b,c(b * 2 ≦ c)を固定して考える。

この時aはb/2以下のDiの個数(x個とする)、dは2c以上のDiの個数(y個とする)通りあるのでb,cを固定した時はxy通りある。

これを全てのb,cの組み合わせについて計算して足せばO(N2)で解くことができる。

  • 満点解法

D_iを昇順にソートしておき、二分探索で、 部分点解法と同様に全てのD_iについてそれの半分以下のD_jの個数と2倍以上のD_jの個数を保存しておく。

満点解法ではbを固定して考える。

aは部分点と同じ方法で何通りあるかわかっている。

c,dの組み合わせが何通りあるか。

c=D_iの時、dはD_i * 2 ≦ D_j を満たすD_jである。

例えば、cがD_i以上の時、c,dの組み合わせは、

D_iの2倍以上のD_jの個数+D(i+1)の2倍以上のDjの個数+D(i+2)の2倍以上のDjの個数+・・・

通りある。これは累積和を用いれば高速に求めることができる。

bごとに最小のcを求め累積和でc,dの組み合わせの数を求めaの個数と掛け算すれば良い。

O(Nlog(N))。

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
#include <cstring>
using namespace std;
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)
#define CLR(mat) memset(mat, 0, sizeof(mat))
typedef long long ll;
const int MOD = 1000000007;
int main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  int N;cin >> N;
  vector<int> D(N); FOR(i,0,N) cin >> D[i];
  sort(D.begin(), D.end());
  int high[N]; CLR(high);
  int low[N]; CLR(low);
  FOR(i,0,N) {
    high[i] = N - (lower_bound(D.begin(), D.end(), D[i] * 2) - D.begin());
    low[i] = upper_bound(D.begin(), D.end(), D[i] / 2) - D.begin();
  }
  int sum[N+1];
  sum[0] = 0;
  FOR(i,0,N) sum[i+1] = (sum[i] + high[i]) % MOD;
  ll ans = 0;
  FOR(i,0,N) {
    int j = lower_bound(D.begin(), D.end(), D[i] * 2) - D.begin();
    ans += (ll)low[i] * (ll)(sum[N]-sum[j]);
    ans %= MOD;
  }
  cout << ans << endl;
  return 0;
}