codeforces 739E - Gosha is hunting
这道题有三种做法,感受一下:
感觉到了歪果仁费尽脑汁想出来的神仙贪心脑洞题被中国人套路算法踩爆的凄凉。。。(我的名字是不是暴露了我的真实实力)
===================================================================================
首先先要明白:有A个A球,B个B球,用了一个A球贡献为ai,B球贡献为bi,两个都用贡献为1-(1-ai)(1-bi)=ai+bi-ai*bi
先讲讲最无脑的费用流吧。。。
显然st先分别流向A球和B球流量为球的个数,费用为0
两种球分别连向所有小精灵,流量为1,费用为贡献
对于小精灵,连向ed分别建一条流量为1费用为0,流量为1费用为-ai*bi的边,这样如果被两种球分别流了,可以减掉多算的
复杂度O(EK)E约等于2n,K是A+B所以大概是O(n^2)带个大常数的,真菜,还是正解有意思
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct node { int x,y,c,next;double d; }a[410000];int len,last[4100]; void ins(int x,int y,int c,double d) { len++; a[len].x=x;a[len].y=y;a[len].c=c;a[len].d=d; a[len].next=last[x];last[x]=len; len++; a[len].x=y;a[len].y=x;a[len].c=0;a[len].d=-d; a[len].next=last[y];last[y]=len; } int st,ed; int pre[4100],c[4100]; double d[4100],ans; int list[4100];bool v[4100]; bool spfa() { memset(d,-63,sizeof(d));d[st]=0;c[st]=(1<<30); memset(v,false,sizeof(v));v[st]=true; int head=1,tail=2;list[1]=st; while(head!=tail) { int x=list[head]; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(a[k].c>0&&d[y]<d[x]+a[k].d&&(!(d[x]+a[k].d-d[y]<=1e-10))) { d[y]=d[x]+a[k].d; c[y]=min(a[k].c,c[x]); pre[y]=k; if(v[y]==false) { v[y]=true; list[tail]=y; tail++;if(tail==4050)tail=1; } } } v[x]=false; head++;if(head==4050)head=1; } if(fabs(d[ed]-d[0])<=1e-10)return false; else { int y=ed;ans+=c[ed]*d[ed]; while(y!=st) { int k=pre[y]; a[k].c-=c[ed]; a[k^1].c+=c[ed]; y=a[k].x; } return true; } } double A[4100],B[4100]; int main() { int n,m,aa,bb; scanf("%d%d%d",&n,&aa,&bb); for(int i=1;i<=n;i++)scanf("%lf",&A[i]); for(int i=1;i<=n;i++)scanf("%lf",&B[i]); len=1;st=n+1,ed=n+2; ins(st,n+3,aa,0),ins(st,n+4,bb,0); aa=n+3,bb=n+4; for(int i=1;i<=n;i++) { ins(aa,i,1,A[i]); ins(bb,i,1,B[i]); ins(i,ed,1,0); ins(i,ed,1,-A[i]*B[i]); } while(spfa()); printf("%.6lf\n",ans); return 0; }
------------------------------------------------------------------------------
这是瑟瑟发抖的官方钦定贪心。。。
大前提:A球和B球都要完全用完,用得越多期望一定越大
考虑先按B的贡献大到小给小精灵排序,然后枚举最后一个B球用的位置i(这个位置必定不会超过A+B),这样右边的只有可能是不用或用a(然并卵)
这只是构建了一个前提,使得1~i之中必定每个小精灵都用了球,如果中间没有选A球替代B球,B球一定是贪心选择前B个的
我们先假设1~i之中全部都只用了B球,再考虑A球的放法
对于两个球都放而言,对于B球用的个数没有影响,对于贡献的影响为a-a*b
而B球只有B个却放了i个,意味着要用i-B次单独选择A球来替换(注意是刚好i-B次),对于贡献的影响为a-b
考虑对于当前1~i再次排序贪心,如果我们把双选放前面,单A放后面,使得这样的选择方案是最优的,我们用邻项交换的方式做一下:
设二次排序后x<y,x小精灵用两个球,y小精灵用A球,则有ax+bx-ax*bx+ay>ay+by-ay*by+ax 即 bx-ax*bx>by-ay*by
所以我们按照b*(1-a)大到小排序,这样就把两种选择方式分成两段了
然而我们还是不知道断点在哪里,所以我们需要枚举断点j
于是:要在j~i中选择前i-B个贡献最大的(贡献是a-b),以及在1~j-1中(贡献是a-a*b)和i+1~n中(贡献为a)找到A-(i-B)个最大的
这是一个求全局前k大的数的和的问题,可以用数据结构解决
我%的是CQzhangyu(全网唯一写这个的)%到吐于是乎顺便跟着也用了对顶堆其实是复制粘贴splay各种操作拼起来不知道哪里出锅了,你也可以写个平衡树来d飞我啊~~~~
复杂度O(n^2logn)跑得可真快呢像我的常数一样快,真棒!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<queue> using namespace std; const double eps=1e-10; struct heap { priority_queue<double>a,b; void del(){while(!b.empty()&&fabs(a.top()-b.top())<=eps)a.pop(),b.pop();} double top(){del();return a.top();} void pop(){del();a.pop();} int size(){return a.size()-b.size();} void push (double x){a.push(x);} void erase(double x){b.push(x);} void clear() { while(!a.empty())a.pop(); while(!b.empty())b.pop(); } }; double sum; struct bst { heap A,B; int lim; void insert(double x) { A.push(-x),sum+=x; if(A.size()>lim) B.push(-A.top()),sum+=A.top(),A.pop(); } void del(double x) { if(B.size()>0&&x<=B.top())B.erase(x); else { sum-=x,A.erase(-x); if(A.size()<lim&&B.size()>0) A.push(-B.top()),sum+=B.top(),B.pop(); } } void clear(){A.clear(),B.clear();} }H[2]; void init(int l1,int l2) { H[0].clear(),H[0].lim=l1; H[1].clear(),H[1].lim=l2; sum=0; } //---------------------------------findkth---------------------------------------------------- struct node{double a,b;}p[2100]; bool cmp(node n1,node n2){return n1.b>n2.b;} bool cmd(node n1,node n2){return n1.b*(1-n1.a)>n2.b*(1-n2.a);} int main() { int n,A,B; scanf("%d%d%d",&n,&A,&B); for(int i=1;i<=n;i++)scanf("%lf",&p[i].a); for(int i=1;i<=n;i++)scanf("%lf",&p[i].b); sort(p+1,p+n+1,cmp); double ss=0;for(int i=1;i<B;i++)ss+=p[i].b; double ans=0; int li=min(n,A+B); for(int i=B;i<=li;i++) { ss+=p[i].b; sort(p+1,p+i+1,cmd); init(i-B,A-(i-B)); for(int j= 1 ;j<=i;j++)H[0].insert(p[j].a-p[j].b); for(int j=i+1;j<=n;j++)H[1].insert(p[j].a); ans=max(ans,ss+sum); for(int j=1;H[0].lim<=i-j;j++) { H[0].del(p[j].a-p[j].b); H[1].insert(p[j].a-p[j].a*p[j].b); ans=max(ans,ss+sum); } } printf("%.6lf\n",ans); return 0; }
----------------------------------------------------------------------
终于进入到了我做这道题的真正目的:练wqs二分
无脑的dp方程:f[i][j][k]表示枚举到第i个小精灵,用了j个A球k个B球
记得我之前讲了啥:A球和B球都要完全用完,用得越多期望一定越大
前面一句联想到wqs二分套路固定选择k个,后面一句说明它的函数值是单调增的,同时用你聪明(???)的脑子想想就知道它是一个上凸包,因为先选的时候肯定是贪心选最大的(注意到这个要求的是最大值,所以二分的值是要减掉的(以前没见过))
因为有两维,我们可以wqs套wqs
你问DP转移?这还用脑子吗?
复杂度O(nlog^2n)也太无聊了,虽然和正解一样又有log又有^2,可是^2在log后面真丑~
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; const double eps=1e-8; int n,A,B; double a[2100],b[2100]; double f[2100],g[2100],h[2100]; void DP(double x,double y) { f[0]=0;g[0]=0;h[0]=0; for(int i=1;i<=n;i++) { f[i]=f[i-1],g[i]=g[i-1],h[i]=h[i-1]; double d2=f[i-1]+a[i]-x; double d3=f[i-1]+b[i]-y; double d4=f[i-1]+a[i]+b[i]-a[i]*b[i]-x-y; if(f[i]<d2&&fabs(d2-f[i])>eps) f[i]=d2, g[i]=g[i-1]+1, h[i]=h[i-1]; if(f[i]<d3&&fabs(d3-f[i])>eps) f[i]=d3, g[i]=g[i-1] , h[i]=h[i-1]+1; if(f[i]<d4&&fabs(d4-f[i])>eps) f[i]=d4, g[i]=g[i-1]+1, h[i]=h[i-1]+1; } } void wqs(double x) { double l=0,r=1,y; while(r-l>eps) { double mid=(l+r)/2; DP(x,mid); if(h[n]<=B) { y=mid; r=mid; } else l=mid; } DP(x,y);f[n]+=B*y; } int main() { scanf("%d%d%d",&n,&A,&B); for(int i=1;i<=n;i++)scanf("%lf",&a[i]); for(int i=1;i<=n;i++)scanf("%lf",&b[i]); double l=0,r=1,ans; while(r-l>eps) { double mid=(l+r)/2; wqs(mid); if(g[n]<=A) { ans=f[n]+A*mid; r=mid; } else l=mid; } printf("%.6lf\n",ans); return 0; }