• P2877 [USACO07JAN]牛校Cow School


    传送门

    $01$规划

    $01$规划优质讲解:传送门

    考虑先将每一科按 $t/p$ 从小到大排序,枚举每一个 $D$(删除的考试数量)

    显然一开始的成绩是 $frac{sum_{i=d+1}^nt[i]}{sum_{i=d+1}^{n}p[i]}$,设它为 $st[D]/sp[D]$

    然后根据$01$规划的套路考虑把所有的成绩 $t[i]$ 减去 $st[D]/sp[D]*p[i]$

    这样做了以后,如果可以使成绩更优,那么说明区间 $[d+1,n]$ 的 $t[i]$ 的最小值小于区间 $[1,d]$ 的 $t[i]$ 的最大值

    (就是说我们本来可以选一个区间 $[1,d]$ 的更优的数,但是被删掉了)

    然后就有一个 $n^2$ 的算法,枚举 $D$,然后把 $t$ 按套路操作,然后求 $[1,d]$ 区间最大值,$[d+1,n]$ 区间最小值,比较一下就好了

    接下来,发现有很多东西是冗余的,没有必要每次都重新算,考虑 $D$ 的变化产生的影响

    想想前面枚举完一个 $D$ 了以后要干嘛,求区间 $[d+1,n]$ 的 $t[i]-st[D]/sp[D]*p[i]$ 的最小值,

    求区间 $[1,d]$ 的 $t[i]-st[D]/sp[D]*p[i]$ 的最大值。

    设 $f[i]$ 表示区间 $[1,i]$,$t$ 变换后的最大值,那么显然 $f[i]=max(t[j]-st[i]/sp[i]*p[j]) , j in [1,i]$,

    发现好像可以斜率优化?

    $st[i]/sp[i]*p[j]+f[i]=t[j]$,那么 $k=st[i]/sp[i],x=p[j],b=f[i],y=t[j] $,原式就变成了 $kx+b=y$ 的形式,可以斜率优化!

    同样的设 $g[i]$ 表示区间 $[i+1,n]$,$t$ 变换后的最小值,那么 $g[i]=min(t[j]-st[i]/sp[i]*p[j]), j in [i+1,n]$,同样可以斜率优化

    可以发现,随着 $i$ 的增加 $k=st[i]/sp[i]$ 是不降的,但是显然 $x$ 是不单调的

    所以我无脑强行上了两遍 $CDQ$ 求 $f$ 和 $g$,貌似有 $O(n)$ 用 $Graham$ 维护凸包的更优解法?

    最后枚举 $D$ ,比较一下 $f[d]$ 和 $g[d]$ 的大小关系就好啦

    具体可以看代码来理解

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<vector>
    using namespace std;
    typedef long long ll;
    typedef double db;
    inline int read()
    {
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    const int N=2e5+7;
    const ll INF=1e18;
    int n,st[N],sp[N];
    db f[N],g[N];
    struct nod {//存每一科的数据 
        int p,t;//分母,分子 
        inline bool operator < (const nod &tmp) const {//按分子除分母排序 
            return t*tmp.p<tmp.t*p;//避免除法,用等价的乘法是好习惯 
        }
    }d[N];
    struct Poi {//斜率优化的点 (或向量) 
        int x,y;
        Poi (int x=0,int y=0) : x(x) , y(y) {}
        inline Poi operator - (const Poi &tmp) const {//点减点就是向量,用向量维护凸包可以避免精度问题,速度也快 
            return Poi(x-tmp.x,y-tmp.y);
        }
    }T[N],tmp[N];
    int Q[N];//存当前凸包 
    inline ll Cross(Poi A,Poi B) { return 1ll*A.x*B.y-1ll*B.x*A.y; }//向量叉积维护凸包 
    inline db calc(int i,int j) { return 1.0*T[j].y-1.0*st[i]/sp[i]*T[j].x; }//计算dp值 
    inline void merge(int l,int r,int mid)//按x归并排序 
    {
        int pl=l,pr=mid+1;
        for(int p=l;p<=r;p++)
        {
            if( pl<=mid && (pr>r||T[pl].x<T[pr].x) ) tmp[p]=T[pl++];
            else tmp[p]=T[pr++];
        }
        for(int p=l;p<=r;p++) T[p]=tmp[p];
    }
    void CDQ_f(int l,int r)//CDQ求f,求i属于[l,r]的f[i]值  
    {
        if(l==r) { f[l]=max(f[l],calc(l,l)); return; }//当前区间只有一个点了,用自己更新自己 
        int mid=l+r>>1,L=1,R=0;
        CDQ_f(l,mid);//先处理左边[l,mid]的所有f 
        //此时左边所有点已经按x排好序了 
        for(int i=l;i<=mid;i++)//维护左边所有点的凸包 
        {
            while( L<R && Cross(T[i]-T[Q[R-1]],T[Q[R]]-T[Q[R-1]])<=0 ) R--; 
            Q[++R]=i;
        }
        for(int i=mid+1;i<=r;i++)//斜率有序,直接用刚刚维护好的凸包更新[mid+1,r]的f 
        {
            while( L<R && calc(i,Q[R-1])>=calc(i,Q[R]) ) R--;
            int j=Q[R]; f[i]=max(f[i],calc(i,j));
        }
        CDQ_f(mid+1,r); merge(l,r,mid);//处理完记得按x排序 
    }
    void CDQ_g(int l,int r)//处理g同理 
    {
        if(l==r) return;//注意g[i]的区间不包含i (i<j<=n)
        int mid=l+r>>1,L=1,R=0;
        CDQ_g(mid+1,r);//注意现在是先处理右边的g了 
        for(int i=mid+1;i<=r;i++)//维护右边的凸包 
        {
            while( L<R && Cross(T[i]-T[Q[R-1]],T[Q[R]]-T[Q[R-1]])>=0 ) R--;
            Q[++R]=i;
        }
        for(int i=l;i<=mid;i++)//更新左边 
        {
            while( L<R && calc(i,Q[L])>=calc(i,Q[L+1]) ) L++;
            int j=Q[L]; g[i]=min(g[i],calc(i,j));
        }
        CDQ_g(l,mid); merge(l,r,mid);//同样记得merge 
    }
    vector <int> ans;//维护答案 
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++) d[i].t=read(),d[i].p=read();
        sort(d+1,d+n+1);//先按t/p排序 
        for(int i=n-1;i;i--) { st[i]=st[i+1]+d[i+1].t; sp[i]=sp[i+1]+d[i+1].p; }//求出st,sp 
        for(int i=1;i<=n;i++)
        {
            T[i]=Poi(d[i].p,d[i].t);//初始化点 
            f[i]=-INF; g[i]=INF;//注意初始值 
        }
        //斜率有序
        CDQ_f(1,n);
        for(int i=1;i<=n;i++) T[i]=Poi(d[i].p,d[i].t);//记得还原回初始值 
        CDQ_g(1,n);
        for(int i=1;i<=n;i++)//枚举d,更新ans 
            if(f[i]>g[i]) ans.push_back(i);
        int len=ans.size();
        printf("%d
    ",len);
        for(int i=0;i<len;i++) printf("%d
    ",ans[i]);
        return 0;
    }
    
    /*
        f[i]=T[j]-st[i]/sp[i]*P[j] j<=i
        st[i]/sp[i]*P[j]+f[i]=T[j] j<=i
        k=st[i]/sp[i],x=P[j],b=f[i],y=T[j]
        max,维护上凸包
        g[i]=T[j]-st[i]/sp[i]*P[j] j>i
        st[i]/sp[i]*P[j]+g[i]=T[j] j>i
        min,维护下凸包
    */
  • 相关阅读:
    XHProf的安装和使用(PHP性能测试神器)
    Visual自动添加CSS兼容前缀
    webpack9--删除冗余的CSS
    Vue 获取自定义属性的值
    webpack8--删除dist目录,压缩分离后的CSS
    webpack7--css压缩成单独的css文件
    APICloud 真机连接失败
    js 实现遮罩某一部分
    js实现选集功能
    微信小程序——wxParse使用方法
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/10747067.html
Copyright © 2020-2023  润新知