题意
题目主要是说有个导师想给 n n n 个学生发消息,他可以给所有学生同时发送 m m m 条消息,然后每个人有导师想发给他看的 m i m_{i} mi 信息和 k i k_{i} ki 次查看消息的次数,如果教授发的消息数小于 k i k_{i} ki 的话,且这 m m m 条信息里有这位学生要看到的 m i m_{i} mi 条消息,那么看到消息的人数就加一(因为一定看得到这条消息)。若是所发的消息数大于 k i k_{i} ki ,且里面有这位学生需要看的消息时,他会随机在 m m m 条消息中选出一个 k i k_{i} ki 条消息的子集,有一定概率看到所需信息,期望数加上概率乘一。现在问怎么选择所发送的消息让所有学生中可以看到自己需要看的信息的期望最大。输出任意一个消息集合即可。
思路
首先观察下所给数据范围,发现有
2
∗
1
0
5
2*10^5
2∗105 个学生,有点大。但是我们发现
k
k
k 值很小,只有
20
20
20 。这说明我们可以从
k
k
k 值入手破这道题。在收到人数期望最大情况下,我们可以发送消息数其实是不大于
20
20
20 的,因为大于
20
20
20 时将不会有任何人概率百分百收到自己需要的信息,而且信息数每增加一条,之前所有人的期望都将下降。所以大胆猜测发送消息数不大于
20
20
20 。(其实是我不会证明) 。
题解
既然可以枚举信息数了,那么我们就可以枚举在同一种信息数时最大接受人数期望是多少。在枚举信息数 i i i 时,对于第 j j j 条需要接受信息的人来说,其概率(期望)就是 min ( 1 , k i ) \min(1,\frac{k}{i}) min(1,ik) ,不大于 1 1 1 是显然的,对于 k i \frac{k}{i} ik 有以下证明:
假设一个人查看次数 k i k_{i} ki少于当前所发信息数n,则其概率可以用组合数求解,为 ( n − 1 k − 1 ) ( n k ) \frac {\binom {n-1}{k-1} }{\binom{n}{k}} (kn)(k−1n−1)(全部方案数为在 n n n 中任取 k k k 条,满足条件的方案数为先取所需要的信息,然后在 n − 1 n -1 n−1 条中取剩下的 k − 1 k-1 k−1 条),然后展开组合数化简即可得到为 k n \frac{k}{n} nk。
然后就可以用冒泡的方式排序最大的 i i i 个信息期望,得出发送数量在 i i i 下的最大接受人数期望和对应的方案。最后在所有发送数中比较取最大值,输出该种方案即可。
PS:为什么用冒泡呢?我们可以发现只是为了找出最大20个数,直接全序列排序复杂度直接起飞,冒泡可以极大减少交换次数。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<double,int> P;
const int maxn=2e5+5;
int n,u,v,w,xk,mx;
double ans;
P pre[30][30];
struct node{
int m,k;
}t[maxn];
vector<int>vt,cnt[maxn];
ll vs[30];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>t[i].m>>t[i].k; mx=max(mx,t[i].m);
cnt[t[i].m].push_back(t[i].k);
}
for(int i=1;i<=20;i++)
{
for(int j=1;j<=mx;j++)
{
double tmp=0;
for(auto k:cnt[j])
{
tmp+=min(1.0,k*1.0/i);
}
int top=i+1;
pre[i][top]=P(tmp,j);
while(pre[i][top]>pre[i][top-1]&&top-1)
{
swap(pre[i][top],pre[i][top-1]),top--;
}
}
}
for(int i=1;i<=20;i++)
{
double tmp=0;
for(int j=1;j<=i;j++)tmp+=pre[i][j].first;
if(ans<tmp)ans=tmp,xk=i;
}
cout<<xk<<"\n";
for(int i=1;i<=xk;i++)cout<<pre[xk][i].second<<" ";
return 0;
}