【Codeforces 626G】Raffles
贪心,堆。
题目大意:
有 $n$ 个奖池,你有 $t$ 张彩票,要下注到奖池里以获得奖金(你可以不全部下注)。
第 $i$ 个奖池里有 $p_i$ 元的奖金,别人已经在这个奖池下注了 $l_i$ 张彩票了。
你在第 $i$ 个奖池里下注 $k_i$ 张彩票能获得的期望奖金为 $p\times \frac{k_i}{l_i+k_i}$ 元。你下注的张数 $k_i$ 必须满足 $k_i\leq l_i$。
你需要合理地分配你的彩票,使得期望获得的总奖金最多。
有 $q$ 个操作,每个操作给定 $u$,会令 $l_u$ 增加 $1$ 或减少 $1$(保证任意时刻有 $l_i\gt 0$)。
要求执行完每步操作后,回答期望总奖金最高是多少。
题解:
考虑没有修改的情况。
先不管每个奖池的奖金,单独考虑一个奖池。
设奖金都为单位 $1$。我们令 $F(l,k)$ 表示在一个奖池中,原来有 $l$ 张,加入 $k$ 张能获得的期望奖金比加入 $k-1$ 张能获得的期望奖金多多少。
当 $l$ 固定时,$F(l,k)$ 的值是关于 $k$ 单调递减的。
所以对于没有修改的情况,我们可以每次贪心地选择插入一张彩票能增加贡献最多的奖池,插入彩票。可以使用堆来对寻找最大值的过程进行加速。时间复杂度 $O((n+t)\log n)$。
考虑修改 $l_i$ 会产生的影响。如果不改变投注的方案,那么答案的变化量是可以 $O(1)$ 计算的。而如果需要修改投注方案,我们显然是把一张最劣的彩票取回,再投注一张最优的彩票。相当于单点修改,全局最值查询,可以使用线段树/堆/ set 等方法实现。
这样单次修改方案的时间复杂度是 $O(\log n)$ 的,我们需要证明,每次 $l_i$ 变化 $1$,最多只会修改 $1$ 张彩票的投注。我们只考虑 $l_i$ 加 $1$,减 $1$ 可以视为它的逆操作。
从贪心的正确性上看,由于我们 $l_i$ 加 $1$ 只会使奖池 $i$ 的彩票变劣,所以替换的只能是奖池 $i$ 中的彩票。设原来投注 $k$ 张,那么原来最后一张的贡献为 $F(l,k)$,说明所有贡献大于 $F(l,k)$ 的都已经被投注了。现在最后一张的贡献为 $F(l+1,k)$,如果没有 $(F(l+1,k),F(l,k)]$ 之间的贡献,那么这张彩票不会被替换。否则,这个奖池的最后一张的贡献为 $F(l+1,k-1)$,严格大于 $F(l,k)$,因此这个奖池不会再被替换。其他奖池因为已经最优因此也不会再替换。故只修改了 $1$ 张彩票。
所以时间复杂度 $O((n+q+t)\log n)$。
Code:
#include<cstdio>
#include<queue>
typedef long long LL;
using namespace std;
const int N=2e5+6,eps=1e-7;
int n,t,q,L[N],a[N],p[N],tm[N];
int nw;
double ans;
struct data{
double d;int id;
inline bool operator<(const data&rhs)const{return d<rhs.d;}
};
struct info{
double d;int id,tim;
inline bool operator<(const info&rhs)const{return d<rhs.d;}
inline bool operator>(const info&rhs)const{return d>rhs.d;}
};
priority_queue<info>last;
priority_queue<info,vector<info>,greater<info> >your;
inline double calc(int i,const int&dlt=0){
return p[i]*(1.*(a[i]+dlt)/(a[i]+L[i]+dlt)-1.*(a[i]+dlt-1)/(a[i]+L[i]+dlt-1));
}
inline double get(int i){
return p[i]*1.*a[i]/(a[i]+L[i]);
}
void init(){
priority_queue<data>q;
for(int i=1;i<=n;++i)q.push((data){p[i]*1./(1+L[i]),i});
while(nw<t&&!q.empty()){
++nw;
data x=q.top();
q.pop();
ans+=x.d;
++a[x.id];
if(a[x.id]!=L[x.id]){
x.d=calc(x.id,1);
q.push(x);
}
}
for(int i=1;i<=n;++i){
if(a[i])
your.push((info){calc(i),i,0});
if(a[i]<L[i])
last.push((info){calc(i,1),i,0});
}
}
int era=0,add=0;
void update(const int&tim){
while(!your.empty()){
info x=your.top();
your.pop();
if(x.tim!=tm[x.id])continue;
--nw;
ans-=x.d;
tm[x.id]=3*tim-1;
last.push((info){x.d,x.id,3*tim-1});
if(--a[x.id])
your.push((info){calc(x.id),x.id,3*tim-1});
break;
}
while(nw<t&&!last.empty()){
info x=last.top();
last.pop();
if(x.tim!=tm[x.id])continue;
++nw;
ans+=x.d;
tm[x.id]=3*tim;
your.push((info){x.d,x.id,3*tim});
if(++a[x.id]<L[x.id])
last.push((info){calc(x.id,1),x.id,3*tim});
}
}
int main(){
scanf("%d%d%d",&n,&t,&q);
for(int i=1;i<=n;++i)scanf("%d",p+i);
for(int i=1;i<=n;++i)scanf("%d",L+i);
init();
add=t;
for(int T=1;T<=q;++T){
int op,i;
scanf("%d%d",&op,&i);
ans-=get(i);
if(op==1)++L[i];else --L[i];
ans+=get(i);
tm[i]=3*T-2;
if(a[i]>L[i])
ans-=calc(i),--nw,--a[i];
if(a[i]){
your.push((info){calc(i),i,3*T-2});
}
if(a[i]<L[i])
last.push((info){calc(i,1),i,3*T-2});
update(T);
printf("%.12f\n",ans);
}
return 0;
}