1. 程式人生 > 實用技巧 >【CF626G】Raffles(貪心)

【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;
}