把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF626G】Raffles(贪心)

点此看题面

  • \(n\)个奖池,第\(i\)个奖池奖金为\(p_i\),已有票数为\(l_i\)
  • 你有\(m\)张彩票可以把它们分配到奖池中,第\(i\)个奖池中分到的票数\(s_i\)需满足\(s_i\le l_i\),且有\(\frac{s_i}{s_i+l_i}\)的概率获得全部奖金\(p_i\)
  • \(q\)次修改,每次将一个奖池中的票数增/减\(1\),求修改后获得奖金的最大期望。
  • \(n,m,q\le2\times10^5,p_i,l_i\le10^3\)

一个经典的贪心问题

这应该是一个比较经典的贪心问题。

显然\(\frac{s_i}{s_i+l_i}\times p_i\)随着\(s_i\)的增加其增长速度是逐渐变慢的,因此我们完全可以每次贪心选择\(\frac{s_i+1}{s_i+1+l_i}\times p_i-\frac{s_i}{s_i+l_i}\times p_i\)最大满足\(s_i<l_i\)\(i\),将其\(s_i\)\(1\)

但现在还有修改。

看似暴力的修改操作

首先注意下几个细节,例如若\(l_i\)增大且原先\(m\)张彩票没有用完则直接使用,若\(l_i\)减小且原先\(s_i\)等于\(l_i\)则必须从中取出一张彩票。

然后就考虑能否从某个奖池中取出彩票给当前奖池,或取出当前奖池的彩票给另外某个奖池,来增大答案。

要实现这个就需要开两个\(set\),分别维护每个奖池增加一张彩票/减少一张彩票给奖金期望值带来的变化。

那么要取彩票肯定取减少一张彩票变化最小的奖池,要给彩票肯定给增加一张彩票变化最大的奖池。

至于复杂度,本来我以为这样暴力做是\(O(ql_ilogn)\)的,然而据说每次修改最多引起一张奖票变化,所以是\(O((n+m+q)logn)\)

代码:\(O((n+m+q)logn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define DB double
#define eps 1e-12
using namespace std;
int n,m,p[N+5],l[N+5],s[N+5];DB ans;
struct Data
{
	int id;DB x;I Data(CI i=0,Con DB& a=1):id(i),x(a){}
	I bool operator < (Con Data& o) Con {return fabs(x-o.x)>eps?x<o.x:id<o.id;}
};set<Data> S[2];
#define Calc(i,s) (1.0*p[i]*(s)/(l[i]+(s)))//计算第i个奖池中放入s张彩票时的期望
#define A(i) Calc(i,s[i]+1)-Calc(i,s[i])//加一张彩票的奖金期望值变化
#define D(i) Calc(i,s[i])-Calc(i,s[i]-1)//减一张彩票的奖金期望值变化
I void Add()//加一张彩票
{
	RI k=(--S[1].end())->id;ans+=(--S[1].end())->x,
	S[1].erase(--S[1].end()),s[k]&&(S[0].erase(Data(k,D(k))),0),//删去原本信息
	++s[k]^l[k]&&(S[1].insert(Data(k,A(k))),0),S[0].insert(Data(k,D(k)));//更新
}
I void Del()//减一张彩票
{
	RI k=S[0].begin()->id;ans-=S[0].begin()->x;
	S[0].erase(S[0].begin()),s[k]^l[k]&&(S[1].erase(Data(k,A(k))),0),//删去原本信息
	--s[k]&&(S[0].insert(Data(k,D(k))),0),S[1].insert(Data(k,A(k)));//更新
}
int main()
{
	RI Qt,i,t=0,op,x,y;for(scanf("%d%d%d",&n,&m,&Qt),i=1;i<=n;++i) scanf("%d",p+i);
	for(i=1;i<=n;++i) scanf("%d",l+i),S[1].insert(Data(i,A(i)));//初始化set
	W(t^m&&S[1].size()) Add(),++t;W(Qt--&&~scanf("%d%d",&op,&x))
	{
		s[x]^l[x]&&(S[1].erase(Data(x,A(x))),0),s[x]&&(S[0].erase(Data(x,D(x))),0);//删去原本信息
		if(ans-=Calc(x,s[x]),op==1) {if(++l[x],t^m) {++s[x],ans+=Calc(x,s[x]),++t;goto End;}}//如果彩票没用完
		else if(--l[x],s[x]>l[x]) {--s[x],ans+=Calc(x,s[x]),S[1].size()?(Add(),0):--t;goto End;}//如果不得不拿出彩票
		ans+=Calc(x,s[x]);W(s[x]&&S[1].size()&&D(x)<(--S[1].end())->x) ans-=D(x),--s[x],Add();//给别的奖池彩票
		W(s[x]^l[x]&&S[0].size()&&A(x)>S[0].begin()->x) ans+=A(x),++s[x],Del();//取别的奖池彩票
		End:s[x]^l[x]&&(S[1].insert(Data(x,A(x))),0),s[x]&&(S[0].insert(Data(x,D(x))),0);//更新信息
		printf("%.15lf\n",ans);
	}return 0;
}
posted @ 2020-12-02 20:10  TheLostWeak  阅读(114)  评论(0编辑  收藏  举报