yukicoder No.398 ハーフパイプ(2)
なんとか時間内に通せてよかった。
問題
6人の審査員がいる。審査員は1人ごとに、0点以上から100点以下の整数の得点を提示する。提示された6つの得点のうち、最大値と最小値を除いた4人分の平均はXであった。このような6人の採点結果は何通りか? ただし、審査員はそれぞれ区別されることに注意せよ。
制約
0.00 <= X <= 100.00
X は小数第二位まで与えられる
答えが0通りになるような入力は与えられない
解法
想定解はDPの埋め込みですが、ここでは順列的な考察(?)による数え上げの解法を紹介します。
愚直に列挙すると、1人について0点から100点までの101通りなので、全てで 通りあります。これを陽に列挙することは難しいので、効率化しなければなりません。そこで、採点結果に登場する数の組み合わせを考え、その組み合わせを各審査員に割り当てる方法の数を足し上げるという戦略を取ることにします。ここで、採点結果に登場する数の組み合わせとは、以下の条件を満たす組
のことです。
まず、最小値と最大値を除く4つの数 () に注目します。題意より
です。4重ループを回せば 条件(1), (2)を満たす
を全列挙できます。これだと計算回数は
回となり若干怪しいですが、条件(2)より
が決まれば
は
で求まるので、実際は計算回数
で済みます*1。
が決まると、
と
は
を満たせばなんでもよいということがわかります。
次に、組 を審査員に割り当てる方法を考えます。例えば
なら、同じものを含む順列の考え方により
通りとなります。分子は
で一定ですが、分母は同じ数がいくつずつあるかによって変わります。つまり分母は「
と
が等しいか」「
と
が等しいか」に依存するので、これらによって場合分けをすればよいです。具体的な式はコードを見てください。
ちなみに、順列の数え上げはサイズが小さければnext_permutationに配列を渡してループ回数を数えるという方法でも良いです(yosupoさんのコードから学んだ)。
#include <bits/stdc++.h> using namespace std; #define int long long // <-----!!!!!!!!!!!!!!!!!!! #define rep(i,n) for (int i=0;i<(n);i++) #define rep2(i,a,b) for (int i=(a);i<(b);i++) #define rrep(i,n) for (int i=(n)-1;i>=0;i--) #define rrep2(i,a,b) for (int i=(b)-1;i>=(a);i--) #define all(a) (a).begin(),(a).end() typedef long long ll; typedef pair<int, int> Pii; typedef tuple<int, int, int> TUPLE; typedef vector<int> V; typedef vector<V> VV; typedef vector<VV> VVV; typedef vector<vector<int>> Graph; const int inf = 1e9; const int mod = 1e9 + 7; int fact[] = {1, 1, 2, 6, 24, 120, 720}; int perm(map<int, int> cnt, int x1, int x2) { if (x1 != -1) cnt[x1]++; if (x2 != -1) cnt[x2]++; int ret = fact[6]; for (auto&& p : cnt) { ret /= fact[p.second]; } return ret; } signed main() { std::ios::sync_with_stdio(false); std::cin.tie(0); double x; cin >> x; int sm = 4 * x; const int MAX = 100; int ans = 0; rep(a1, MAX + 1) { rep2(a2, a1, MAX + 1) { rep2(a3, a2, MAX + 1) { int a4 = sm - a1 - a2 - a3; if (!(a3 <= a4 && a4 <= MAX)) continue; map<int, int> cnt; cnt[a1]++; cnt[a2]++; cnt[a3]++; cnt[a4]++; if (a1 != 0 && a4 != MAX) ans += perm(cnt, -1, -1) * a1 * (MAX - a4); if (a4 != MAX) ans += perm(cnt, a1, -1) * (MAX - a4); if (a1 != 0) ans += perm(cnt, a4, -1) * a1; ans += perm(cnt, a1, a4); } } } cout << ans << endl; }
*1:yukicoderなら108でも十分通るので4重ループで書いてもよかった