【JOISC2019|2019】【20190622】cake3

题目

\(N\) 个物品中选\(M\)个,排列成一个环:\(k_1,\cdots,k_M\)价值为:
\[ \sum_{j=1}^{N}{V_i} - \sum_{j=1}^{M}|C_{k_j}- C_{k_{j\%M+1} }| \]
$3 \le N,M \le 2\times 10^5 $

题解

  • 对于一个\(k\)\(C\)最小的排列的贡献是\(2\ ( max - min)\)

  • 因为将 $ k $ 按 $ C $ 排序,由于最终是一个环, $ C_{k_j} - C_{k_{j+1}} $ 一定会被至少经过两次

  • 将物品按\(C\)排序,枚举最大点,枚举最小点,中间选\(V\)最大的

  • 可以发现从左向右枚举右端点,最小值的选择是单调的

  • 但是单调可能是不连续的,接下来有一个套路:

  • 计算\([l,r]\)的值时,可以先找\(mid = (l + r) /2\) 的最优决策点\(pos_{mid}\)

  • 那么\(pos_{[l,mid-1]} \le pos_{mid} \ , \ pos_{[mid+1,r]} \ge pos_{mid}\) ,分治求下去

  • 需要查找区间前\(m-2\)大的和,如果用主席树实现

  • 时间复杂度:\(O(N \log^2 N )\)

    #include<bits/stdc++.h>
    #define ll long long 
    #define inf 1e18
    
    using namespace std;
    
    const int N=200010,M=20;
    int n,m,st[N],tp,sub[N],tot,sz,cnt[N*M],ls[N*M],rs[N*M],rt[N];
    ll sum[N*M],a[N],b[N],ans=-inf;
    struct data{int x,y;}A[N];
    bool operator <(data X,data Y){return X.y<Y.y;}
    
    void ins(int&k,int lst,int l,int r,int x,int y){
      sum[k=++sz]=sum[lst]+y;cnt[k]=cnt[lst]+1;
      ls[k]=ls[lst],rs[k]=rs[lst];
      if(l==r)return;
      int mid=(l+r)>>1;
      if(x<=mid)ins(ls[k],ls[lst],l,mid,x,y);
      else ins(rs[k],rs[lst],mid+1,r,x,y);
    }
    
    ll query(int k,int lst,int l,int r,int x){
      if(l==r||!x)return 1ll*sub[l]*x;
      int mid=(l+r)>>1,tmp=cnt[rs[k]]-cnt[rs[lst]];
      if(x<tmp)return query(rs[k],rs[lst],mid+1,r,x);
      return sum[rs[k]]-sum[rs[lst]]+query(ls[k],ls[lst],l,mid,x-tmp);
    }
    
    ll cal(int l,int r){
      if(r-l+1<m)return -inf;
      return a[l]+b[r]+query(rt[r-1],rt[l],1,tot,m-2);
    }
    
    void solve(int L,int R,int l,int r){
      if(L>R)return;
      if(l==r){for(int i=L;i<=R;++i)ans=max(ans,cal(st[l],i));}
      int Mid=(L+R)>>1,mid=l;ll tmp=-inf;
      for(int i=l;i<=r;++i){
          ll now=cal(st[i],Mid);
          if(tmp<now)tmp=now,mid=i;
      }
      ans=max(ans,tmp);
      solve(L,Mid-1,l,mid);
      solve(Mid+1,R,mid,r);
    }
    
    int main(){
      freopen("cake3.in","r",stdin);
      freopen("cake3.out","w",stdout);
    
      scanf("%d%d",&n,&m);
      for(int i=1;i<=n;++i){
          scanf("%d%d",&A[i].x,&A[i].y);
          sub[++tot]=A[i].x;
      }
    
      sort(sub+1,sub+tot+1);
      tot=unique(sub+1,sub+tot+1)-sub-1;
      sort(A+1,A+n+1);
    
      for(int i=1;i<=n;++i){
          int pos=lower_bound(sub+1,sub+tot+1,A[i].x)-sub;
          ins(rt[i],rt[i-1],1,tot,pos,A[i].x);
    
          a[i]=A[i].x+2ll*A[i].y;
          b[i]=A[i].x-2ll*A[i].y;
          if(!tp||a[i]>a[st[tp]])st[++tp]=i;
      }
      solve(m,n,1,tp);
      /*ll ans=cal(1,m);
      for(int i=m,j=1;i<=n;++i){
          while(j<tp&&st[j+1]+m-1<=i&&cal(st[j],i)<cal(st[j+1],i))j++;
          ans=max(ans,cal(st[j],i));
          for(int k=1;k<=tp&&st[k]+m-1<=i;++k){
              printf("%lld ",cal(st[k],i));
          }
          puts("");
      }*/
      //这段是不对的,因为并不是所有单调都是连续的
      //像斜率优化的那种可以用单调队列维护的只是单调的一种特殊情况
      //不单调可以用分治做
      //20190623
      cout<<ans<<endl;
      return 0;
    }
    

转载于:https://www.cnblogs.com/Paul-Guderian/p/11073929.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值