題目大意
給你
每個獎池的獎金爲
每次修改,可以把一個獎池的彩票數+1或-1。
每次修改後問投獎所能獲得的獎金的最大期望,每個獎池投獎的彩票數不能超過總彩票數的一半。
思路
在同一個獎池中,後一張彩票的的貢獻一定比前一張的貢獻要小,我們可以用線段樹維護,一個區間貢獻最大的點的位置,以及減小一張彩票所減小的貢獻最小的點。那麼如果正貢獻-減小的貢獻>0,那麼,就可以轉移一張彩票。直到不能滿足條件爲止。
參考程序
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define maxn 200005
#define oo 1e15
#define db double
#define lim 1e-12
using namespace std;
int n,m,q;
db p[maxn];
int a[maxn],b[maxn];
struct seg{
db win,lose,ans;
int w1,w2;
}t[maxn*4];
seg open(int w){
seg ret;
ret.ans=p[w]*a[w]/(a[w]+b[w]);
ret.ans=min(ret.ans,p[w] / 2);
if (a[w]>=b[w]) ret.win=0;
else ret.win=p[w]*(a[w]+1) / (a[w]+b[w]+1) - p[w] * a[w] / (a[w]+b[w]);
if (a[w]==0) ret.lose=oo;
else if (a[w]>b[w]) ret.lose=0;
else ret.lose=p[w]*a[w] / (a[w]+b[w]) - p[w]*(a[w]-1) / (a[w]+b[w]-1);
ret.w1=ret.w2=w;
return ret;
}
void update(int v){
t[v].ans=t[v << 1].ans+t[v << 1 | 1].ans;
t[v].win=max(t[v << 1].win,t[v << 1 | 1].win);
if (t[v].win==t[v << 1].win) t[v].w1=t[v << 1].w1;
else t[v].w1=t[v << 1 | 1].w1;
t[v].lose=min(t[v << 1].lose,t[v << 1 | 1].lose);
if (t[v].lose==t[v << 1].lose) t[v].w2=t[v << 1].w2;
else t[v].w2=t[v << 1 | 1].w2;
}
void build(int v,int l,int r){
if (l==r) {
t[v]=open(l);
return;
}
int mid=(l+r) >> 1;
build(v << 1,l,mid);
build(v << 1 | 1,mid+1,r);
update(v);
}
void change(int v,int l,int r,int x){
if (l==r) {
t[v]=open(l);
return;
}
int mid=(l+r) >> 1;
if (x<=mid) change(v << 1,l,mid,x);
else change(v << 1 | 1,mid+1,r,x);
update(v);
}
int main(){
scanf("%d%d%d",&n,&m,&q);
fo(i,1,n) scanf("%lf",&p[i]);
fo(i,1,n) scanf("%d",&b[i]);
build(1,1,n);
fo(i,1,m) {
int now=t[1].w1;
a[now]++;
change(1,1,n,now);
}
fo(i,1,q) {
int x,y;
scanf("%d%d",&x,&y);
if (x==1) b[y]++;
else b[y]--;
change(1,1,n,y);
while (t[1].win-t[1].lose>lim) {
int now1=t[1].w1,now2=t[1].w2;
a[now1]++;
a[now2]--;
change(1,1,n,now1);
change(1,1,n,now2);
}
printf("%.10lf\n",t[1].ans);
}
return 0;
}