• 洛谷 P2317 [HNOI2005]星际贸易 解题报告


    P2317 [HNOI2005]星际贸易

    题目描述

    输入输出格式

    输入格式:

    输出格式:

    如果可以找到这样的方案,那么输出文件output.txt中包含两个整数X和Y。X表示贸易额,Y表示净利润并且两个数字之间用一个空格隔开。如果不能完成这次星际贸易,那么输出文件output.txt中包含 “Poor Coke!”(不包括引号)。


    不知道为什么网上找不到txt的,于是只好copy了洛谷的,侵删。


    首先理清一下题目。

    这老哥的第一目的是想让自己卖的钱最多,第二目的是在卖的钱最多的基础上把利润搞大一点。

    很容易发现,第一目的就是个普通的01背包,而且题目说了有唯一解。

    (is[i][j])存储第(i)个星球(j)状态时是否由在(i)上卖东西得到的,得到必须去的星球。

    第二目的就比较坑了,经过仔细的读题,我们发现这个维护是个捆绑销售啊,你只要上去星球了,你就必须得维护。

    虽说这样比较无良,但不可置否的,也给我们做题带来了方便啊,我们可以对维护少一些决策。

    我们看看这个燃料,虽然他能带的上看起来很多,但实际上也只需要最多4000就够了,我们试试对燃料做个背包。

    (dp[i][j])代表在第(i)个星球正准备出发(出发的加速还未使用)时,拥有(j)燃料的花费最小值。

    (dp[i][j]=min(dp[i][j],dp[k][j+2+cnt]+cnt*p[i]+f[i]),i>k,l[i]-l[k]>=l0)

    转移方程如上,要枚举$i,j,k,cnt$4维,绝对爆了

    怎么优化?

    对完全背包熟悉的话,也许比较容易就可以优化到(O(N^3))

    (dp[i][j]=min(dp[i][j-1]+p[i],dp[k][j+2]+f[i]),i>k,l[i]-l[k]>=l0)

    (k)这一维怎么办呢?
    我们发现,对于每一次使用,我们其实都找了很多遍(dp[k][j+2])的最小值。

    我们为什么不能把之前找到给利用起来呢?

    好了,单调队列维护一下。

    至此,复杂度就降到了(O(N^2))


    code:

    #include <cstdio>
    #include <cstring>
    int min(int x,int y) {return x<y?x:y;}
    const int N=2002;
    const int inf=0x3f3f3f3f;
    int dp[N][(N<<1)+10];
    int n,m,r,l0;//点,货物,燃料,距离
    int a[N],b[N],l[N],p[N],f[N];//量,钱,距,燃,维修
    //此时dp[i][j]代表前i个星球卖j货物时的最大值
    int is[N][N];//是否在i星球j状态卖东西
    int is_s[N],m_max,m_min;
    void s_dp()
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(j>=a[i]&&dp[i][j]<dp[i-1][j-a[i]]+b[i])
                {
                    dp[i][j]=dp[i-1][j-a[i]]+b[i];
                    is[i][j]=1;
                }
            }
        int j;
        for(int i=0;i<=m;i++)
            if(dp[n][i]>m_max)
            {
                m_max=dp[n][i];
                j=i;
            }
        for(int i=n;i>0;i--)
        {
            if(is[i][j])
            {
                j-=a[i];
                is_s[i]=1;
            }
        }
        is_s[n]=1;
    }
    //此时dp[i][j]代表前i个星球装j燃料时正准备出发的费用
    int q[(N<<1)+5][N][2],L[N],R[N];//0首1尾
    void push(int j,int c,int id){q[j][++R[j]][0]=c;q[j][R[j]][1]=id;}
    void front_pop(int j){R[j]--;}
    void back_pop(int j){L[j]++;}
    bool is_empty(int j) {return L[j]==R[j];}
    void p_dp()
    {
        memset(dp,0x3f,sizeof(dp));
        m=min(N,r);
        dp[0][m]=0;
        push(m,0,0);
        for(int i=1;i<=n;i++)
            for(int j=0;j<=m;j++)
            {
                while(!is_empty(j+2)&&l[i]-l[q[j+2][L[j+2]+1][1]]>l0)
                    back_pop(j+2);//超出距离
                int id1=q[j+2][L[j+2]+1][1];//停的可行性最小值坐标
                dp[i][j]=min(dp[id1][j+2]+f[i],dp[i][j]);//从前面停
                if(p[i]&&j)
                    dp[i][j]=min(dp[i][j],dp[i][j-1]+p[i]);//或者买油
                if(is_s[i])
                    L[j]=R[j]=0;
                while(!is_empty(j)&&dp[i][j]<=q[j][R[j]][0]) front_pop(j);//更新单队
                push(j,dp[i][j],i);
            }
        m_min=inf;
        for(int i=0;i<=m;i++)
            m_min=min(dp[n][i],m_min);
    }
    int main()
    {
        scanf("%d%d%d%d",&n,&m,&r,&l0);
        for(int i=1;i<=n;i++)
            scanf("%d%d%d%d%d",a+i,b+i,l+i,p+i,f+i);
        for(int i=1;i<=n;i++)
            if(l[i]-l[i-1]>l0)
            {
                printf("Poor Coke!
    ");
                return 0;
            }
        s_dp();
        p_dp();
        if(m_min<inf)
            printf("%d %d
    ",m_max,m_max-m_min);
        else
            printf("Poor Coke!
    ");
        return 0;
    }
    
    

    事实上要注意几个细节:

    1. 到第一问求得星球上时要清空单队(以前的情况失效了)
    2. 判能不能到不是看他亏不亏。
    3. 写的巧一点可以不用先判一次无解(有的状态拿0转移了)

    2018.5.26

  • 相关阅读:
    inflate
    【Android布局】在程序中设置android:gravity 和 android:layout_Gravity属性
    安卓延时执行代码
    Listview控件
    安卓的progress
    android studio的弹出层
    解决mysql的日志文件过大的问题
    linux查文件大小
    乔坟往事-姑妄言之
    乔坟往事-村里人家
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9094703.html
Copyright © 2020-2023  润新知