JZOJ 5432. 【NOIP2017提高A组集训10.28】三元组

题目大意

给定 X+Y+Z 个三元组 (xi,yi,zi) ,每个三元组只能选 xi,yi,zi 的其中一个并统计入答案,问恰好选X个 xi ,Y个 yi ,Z个 zi 的答案的最大值。
数据范围
对于100%的数据满足,1<=X+Y+Z<=500000,0<=x[i],y[i],z[i]<=500000.

题解

对于 1<=X+Y+Z<=100 显然DP。(设 f[i][j][k] 表示目前选了i个x,j个y,k个z)
100%的做法,有很多种。
对于X=0,按照 yizi 来排个序,选择前Y个的 yi ,其余的选 zi
X>0 的情况,三元组太复杂了,将它变成二元组吧。
即将所有x选了,三元组变成 (0,yixi,zixi)
然后根据选y还是选z的优越性来做文章。
审一波题目的条件:
①三元组内只能够选一个元素。
②选择元素要考虑全局最优。
③选择每类的元素个数受到了限制。
所以我们设计的算法必须要考虑到每一种情况(即每个元素究竟选x/y/z)。
如果没有条件③,那么直接排序,判断一下这个三元组有没被选择过元素就好了。
考虑将三个限制变为两个限制。所以一开始x全部选,然后三元组变二元组。
接下来再考虑选哪些y,哪些z对答案有利。
所以要确定哪些y-x要被选,哪些z-x要被选.(一种情况讨论不完,所以要枚举分界点讨论哪种情况最优)
Alan的方法:按照y-x从大到小排,枚举分界点i,讨论每种i前面选y-x或z-x的情况。他选择在i之前选y或z而不选x,是因为如果选了x,那么第i位选y没有前面那位选y那么优。如果第i位选择了x,那么第i位选z的情况就会没有被讨论到。
即前i位中选前Y大的y-x,剩下的i-Y个选z,后面前Z-i+Y位z-x也选z。
用数据结构维护很简单,而我比较蒟蒻。我种了3棵线段树,不仅MLE还TLE……
题解方法:
也是枚举一个界点i,i前面的选前Z个z-x,i后面的选前Y个y-x。
(每种情况都要让选的z和y都对答案有利)
这里写图片描述
不管怎么样,都要选择分界点前/后的前几个元素。而i每往右移,就是加了个z-x减了个y-x,那么i前面第Z位的z-x不会递减,同样的,i后面第Y位的y-x因为前Y位的y-x可能被删掉而不会递增。
所以排序之后用两个指针维护一下就好了。(指针难打?)
先将y-x和z-x还有z-y排好序,然后先选排名前Z的z-y。
用bool[]维护这个元素是否能被选。

总结

减少制约条件。当制约条件过多的时候,尝试减少一个(些)比如说这题考虑作差,根据y、z与x的差,来尽量使答案变优。
②这道题最主要的难点是选的时候无法一次性满足所有条件,所以考虑分类讨论,每类考虑出一种最优的解,从而最优答案就是每种最优解的最优解。
程序实现方面:这道题目实现起来也是非常麻烦的(调了我几个小时/(ㄒoㄒ)/~~)困惑我的地方主要是不知道怎么确定前Z个z-x和前y个y-x。
对策:可以排个序,顺便维护是哪个三元组(维护一个原来的位置wz),然后用bool[]维护这个元素是否能被选。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define N 500010
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    LL d,id;
};note y2[N],z2[N],n2[N];
struct note1{
    LL yf,zf;
};note1 a[N];
LL i,j,X,Y,Z,n,ans;
LL sum,temp,temp1;
LL x[N],y[N],z[N];
LL num[N],ranky[N],rankz[N],rank[N];
LL zy,zz,wz,sy,sz,cnt;
bool by[N],bz[N];
LL read(){
    LL fh=1,res=0;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return res*fh;
}
bool cmp(note x,note y){return x.d>y.d;}
bool cmpy(note x,note y){return x.d>y.d || (x.d==y.d && rank[x.id]<rank[y.id]);}
bool cmpz(note x,note y){return x.d>y.d || (x.d==y.d && rank[x.id]>rank[y.id]);}
int main(){
    X=read(),Y=read(),Z=read();
    n=X+Y+Z;
    fo(i,1,n)x[i]=read(),y[i]=read(),z[i]=read(),sum+=x[i]; 
    fo(i,1,n){
        y2[i].id=z2[i].id=n2[i].id=i;
        a[i].yf=y2[i].d=y[i]-x[i];
        a[i].zf=z2[i].d=z[i]-x[i];
        n2[i].d=z[i]-y[i];
        by[i]=1;
    }
    sort(n2+1,n2+n+1,cmp);
    fo(i,1,n)rank[n2[i].id]=i;
    sort(y2+1,y2+n+1,cmpy);
    sort(z2+1,z2+n+1,cmpz);
    fo(i,1,Z)sz+=a[n2[i].id].zf,bz[n2[i].id]=1,by[n2[i].id]=0;
    zy=0,zz=n;cnt=0;
    while(zy<n){
        if(cnt==Y)break;
        zy++;
        if(by[y2[zy].id]){
            cnt++;
            sy+=y2[zy].d;
        }
    }
    ans=sum+sy+sz;
    fo(i,Z+1,n-Y){
        wz=n2[i].id;
        bz[wz]=1;
        by[wz]=0;
        if(a[wz].zf>=z2[zz].d){
            sz+=a[wz].zf;
            while(!bz[z2[zz].id])zz--;//找到目前第Z位,然后删掉它
            sz-=z2[zz].d;
            zz--;
        }
        if(a[wz].yf>=y2[zy].d){
            sy-=a[wz].yf;
            cnt--;
            while(zy<n){
                if(cnt==Y)break;
                zy++;
                if(by[y2[zy].id]){
                    cnt++;
                    sy+=y2[zy].d;
                }
            }
        }
        ans=ans>sum+sy+sz?ans:sum+sy+sz;
    }
    printf("%lld",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值