Educational Codeforces Round 117. CF1612 E. Messages


CF1612E. Messages

题目描述

\(n\) 个学生,他们只有被提醒时才回去阅读通知,如果被通知次数不超过 \(k_i\),他们一定会去阅读通知,否则他们每次阅读通知的几率为 \(\frac{k_i}{通知次数}\) ,即阅读的总通知数的期望为 \(k_i\)

现在对于每一个学生,他们需要阅读标号为 \(m_i\) 的通知,你该在哪几条通知进行提醒,使被阅读的通知数期望最大。

\(1 \le n \le 2 \cdot 10^5\)

\(1 \le m_i \le 2 \cdot 10^5\)

\(1 \le k_i \le 20\)


解题思路

阅读体面发现 \(k_i\) 不超过20,当提醒次数大于20时,期望值肯定小于提醒20次时的期望(因为分母被增大了)。所以我们可以考虑枚举所有1-20的提醒次数。

设提醒次数为 \(t\)

对于每一个学生 \(i\),提醒 \(m_i\) 对答案期望的贡献分为两种情况。① \(t \le k_i\) ,这时这个通知必定阅读,期望贡献为 \(1\)。② \(k_i < t\) ,这是通知被阅读的概率为 \(\frac{k_i}{t}\)。所以综上,通知被阅读的概率为 \(\frac{min(t,k_i)}{t}\)

为了使期望最大,我们计算出每一个通知被提醒的期望贡献,并将它们降序排序,选取前 \(t\) 个,得到总期望。

进行20次循环,得到最大的期望,输出答案。

过程中的期望计算涉及分数,我们可以将分子分母分别储存,在比较时通过 \(分母a*分子b < 分母b * 分子a\)的方式进行比较。


代码

#include
using namespace std;
using pii = pair;
const int maxn = 2e5;
int k[maxn + 10];
int m[maxn + 10];
void solve() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> m[i] >> k[i];
	}

	pii ansnum = {0,1};
	vector ans;
	for (int i = 1; i <= 20; i++) {
		vector cnt(maxn);
		vector p;
		for (int j = 1; j <= n; j++) {
			cnt[m[j]] += min(i, k[j]);
		}
		for (int j = 0; j < maxn; j++)
			p.push_back(make_pair(cnt[j], j));
		sort(p.begin(), p.end(), greater());
		pii cur = { 0,i };
		vector anst;
		for (int j = 0; j < i; j++)
			cur.first += p[j].first, anst.push_back(p[j].second);
		if (ansnum.first * cur.second < cur.first * ansnum.second) {
			ansnum = cur;
			ans = anst;
		}
	}
	cout << ans.size() << "\n";
	for (auto i : ans) {
		cout << i << ' ';
	}
	cout << "\n";
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	solve();
	return 0;
}