CF229E 题解
前言
基本是官方题解的思路。
分析
先考虑不用纠结的情况。假设第 \(n+1\) 大价值的物品的价值小于第 \(n\) 大的。
此时物品名称集合可以确定。
对于每类名称,恰好拿到的概率为 \(1/\dbinom{k_i}{a_i}\),\(a_i\) 为选择的物品数量,\(k_i\) 为总数。
对于不同类名称,鱼的选择间独立。将这些概率乘起来就行了。
再考虑要纠结的情况。假设和第 \(n\) 大价值的物品相同价值的是第 \(l\) 到 \(r\) 个,\(l \le n \lt r\)。
一种做法是,暴力求出所有 \(\dbinom{r-l+1}{n-l+1}\) 种情况,对于它们的概率取平均值。
对于所有种情况的概率之和,可以 dp 求出。具体地,设 \(dp(i,j)\) 为 \([l, l+i)\) 中的物品,选了 \(j\) 个,所有合法情况的概率之和,那么 \(dp(r-l+1, n-l+1)\) 即为答案。
对于一个状态 \((i,j)\),可以从 \((i-1, j)\)(不选)和 \((i-1, j-1)\)(选)转移来。
考虑一个名称。当我想从选 \(a_i\) 个变为选 \(a_i+1\) 个时,概率变化为 \({p_i \dbinom{k_i}{a_i}}/{\dbinom{k_i}{a_i+1}} = \dfrac{p_i(a_i+1)}{k_i-a_i}\),\(p_i\) 是原先的概率。
最后别忘了除以情况数 \(\dbinom{r-l+1}{n-l+1}\)。
#include <bits/extc++.h>
#define UP(i,s,e) for(auto i=s; i<e; ++i)
#define IT(i,s,e) for(auto i=s; i!=e; ++i)
#define pbds __gnu_pbds
using std::cin; using std::cout;
namespace m{ // }{{{
template<typename T> using Tree = pbds::tree<T, pbds::null_type, std::less<>, pbds::rb_tree_tag, pbds::tree_order_statistics_node_update>;
constexpr int N = 1e3, M = 1e3; int in, im; Tree<std::pair<int, int>> gift_rk;
std::vector<int> gifts[M];
int choose[M];
double dp[N+10][N+10];
double inv_binom(int x, int y){
double ans = 1;
UP(i, 0, y){
ans *= (y-i)*1.0/(x-i);
}
return ans;
}
void work(){
cin >> in >> im;
UP(i, 0, im){
int ik; cin >> ik;
UP(j, 0, ik){
int val; cin >> val;
gift_rk.insert({-val, i});
gifts[i].push_back(val);
}
}
auto beg = gift_rk.lower_bound({gift_rk.find_by_order(in)->first, -1});
auto end = gift_rk.lower_bound({gift_rk.find_by_order(in)->first, 114514});
int len = std::distance(beg, end);
IT(i, gift_rk.begin(), beg){
choose[i->second]++;
in--;
}
double &ans = dp[0][0]; ans = 1;
UP(i, 0, im){
ans *= inv_binom(gifts[i].size(), choose[i]);
}
auto it = beg;
UP(i, 1, len+1){
UP(j, 0, in+1){
dp[i][j] = dp[i-1][j];
if(j>0){
dp[i][j] += dp[i-1][j-1]
* (choose[it->second]+1)
/ (gifts[it->second].size() - choose[it->second]);
}
}
it++;
}
printf("%.10lf", dp[len][in] * inv_binom(len, in));
}
} // {}}}
int main(){m::work(); return 0;}

浙公网安备 33010602011771号