[BZOJ4700]适者(CDQ分治+DP/李超线段树)

如果没有秒杀,就是经典的国王游戏问题,按t/a从小到大排序即可。

考虑删除两个数i<j能给答案减少的贡献:S[i]*T[i]+P[i-1]*A[i]-A[i]+S[j]*T[j]+P[j-1]*A[j]-A[j]-T[i]*A[j]

其中P为T=(D-1)/ATK+1的前缀和,S为A的后缀和。

我们设bi=S[i]*T[i]+P[i-1]*A[i]-A[i],考虑当i固定时,j可能取什么值。

 

解法一:CDQ分治

不难发现,当i<j<k时k比j优的充要条件是b[k]-T[i]*A[k]>b[j]-T[i]*A[j],即T[i]<(b[k]-b[j])/(A[k]-A[j])。

不难看出斜率优化的模型,最后要最大化的是b[k]-T[i]*A[k]即所有点(A[k],b[k])以-T[i]的斜率投影到y轴上的最高点,于是答案一定在上凸壳上。

注意到这里点的横坐标A和询问斜率T均不单调,需要CDQ分治。

每次分治左半边按T排序,右半边按A排序并将凸包建好,然后左端点从前往后扫即可。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 typedef long long ll;
 5 using namespace std;
 6 
 7 const int N=300010;
 8 int n,m,q[N];
 9 ll A,B,ans,tot,c[N];
10 struct P{ int x,y; ll z; }a[N],b[N];
11 
12 bool cmp(const P &a,const P &b){ return a.x*b.y>b.x*a.y; }
13 bool cmp2(const P &a,const P &b){ return a.y>b.y; }
14 bool cmp3(const P &a,const P &b){ return a.x<b.x; }
15 ll calc(int x,int y){ return tot-a[x].z-a[y].z+a[y].x*a[x].y; }
16 double sl(int x,int y){ return (double)(a[y].z-a[x].z)/(a[y].x-a[x].x); }
17 
18 void solve(int l,int r){
19     if (l>=r) return;
20     int mid=(l+r)>>1;
21     solve(l,mid); solve(mid+1,r);
22     sort(a+l,a+mid+1,cmp2); sort(a+mid+1,a+r+1,cmp3);
23     int st=0,ed=0;
24     rep(i,mid+1,r){
25         while (st<ed && sl(q[ed-1],q[ed])<sl(q[ed],i)) ed--;
26         q[++ed]=i;
27     }
28     rep(i,l,mid){
29         while (st<ed && sl(q[st],q[st+1])>a[i].y) st++;
30         ans=min(ans,calc(i,q[st]));
31     }
32 }
33 
34 int main(){
35     freopen("bzoj4700.in","r",stdin);
36     freopen("bzoj4700.out","w",stdout);
37     scanf("%d%d",&n,&m);
38     rep(i,1,n) scanf("%d%d",&a[i].x,&a[i].y),A+=a[i].x,a[i].y=(a[i].y-1)/m+1;
39     sort(a+1,a+n+1,cmp);
40     rep(i,1,n){
41         A-=a[i].x; B+=a[i].y;
42         a[i].z=a[i].x*(B-1)+A*(a[i].y);
43         tot+=a[i].x*(B-1);
44     }
45     ans=tot; solve(1,n); printf("%lld\n",ans);
46     return 0;
47 }

 

解法二:李超树

不难发现,i固定时,j对答案减小的贡献是b[j]-T[i]*A[j],这个式子可以理解为直线y=-A[j]*x+b[j]在T[i]处的总坐标。

问题转化为,i从大到小枚举,需要支持插入一条直线与在线查询某个横坐标上所有直线总坐标最大值。

动态维护凸包问题,显然用李超树。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define ls (x<<1)
 4 #define rs (ls|1)
 5 #define lson ls,L,mid
 6 #define rson rs,mid+1,R
 7 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 8 typedef long long ll;    
 9 using namespace std;
10 
11 const int N=300010;
12 int n,m,v[N<<2];
13 ll tot,ans,res,L[N],S[N],k[N],b[N];
14 struct P{ int a,t; }p[N];
15 bool operator <(const P &a,const P &b){ return a.a*b.t>b.a*a.t; }
16 bool pd(int x,int y,int p){ return k[x]*p+b[x]>k[y]*p+b[y]; }
17 ll calc(int x,int p){ return k[x]*p+b[x]; }
18 
19 void ins(int x,int L,int R,int p){
20     if (L==R){ if (pd(p,v[x],L)) v[x]=p; return; }
21     int mid=(L+R)>>1;
22     if (k[p]>k[v[x]]){
23         if (pd(p,v[x],mid)) ins(lson,v[x]),v[x]=p; else ins(rson,p);
24     }else{
25         if (pd(p,v[x],mid)) ins(rson,v[x]),v[x]=p; else ins(lson,p);
26     }
27 }
28 
29 void que(int x,int L,int R,int k){
30     res=max(res,calc(v[x],k));
31     if (L==R) return;
32     int mid=(L+R)>>1;
33     if (k<=mid) que(lson,k); else que(rson,k);
34 }
35 
36 int main(){
37     freopen("bzoj4700.in","r",stdin);
38     freopen("bzoj4700.out","w",stdout);
39     scanf("%d%d",&n,&m);
40     rep(i,1,n) scanf("%d%d",&p[i].a,&p[i].t),p[i].t=(p[i].t-1)/m+1;
41     sort(p+1,p+n+1);
42     rep(i,1,n) L[i]=L[i-1]+p[i].t;
43     for (int i=n; i; i--) S[i]=S[i+1]+p[i].a;
44     rep(i,1,n) k[i]=-p[i].a,b[i]=S[i]*p[i].t+L[i-1]*p[i].a-p[i].a,tot+=p[i].t*S[i]-p[i].a;
45     ins(1,1,n,n);
46     for (int i=n-1; i; i--)
47     res=0,que(1,1,n,p[i].t),ans=max(ans,b[i]+res),ins(1,1,n,i);
48     printf("%lld\n",tot-ans);
49     return 0;
50 }

 

posted @ 2019-01-10 19:22  HocRiser  阅读(410)  评论(0编辑  收藏  举报