• (个人题目)作业 题解


    题目链接

    本题是燕老师&林队&WMY的七拼八凑赛(mathrm{T3}).

    搬运自我的洛谷博客


    Orz 隔壁机房杜爷首 A & 最优解

    (mathrm{Orz space qwaszx、eee})(mathrm{hoho、dozenX、Tian})(mathrm{Xing、zrzring space 5}) 位大佬 (mathrm{AC space \% \% \%})


    为了证明自己是个良心出题人,(mathrm{lxy}) 出了这样一道签到题。

    5pts

    人口普查。

    #include <iostream>
    using namespace std;
    
    int main()
    {
        cout << "171128";
        return 0;
    }
    

    40pts

    打一个暴力就能过。

    至于这个暴力怎么打,(mathrm{lxy}) 小菜鸡也没有试过,这个问题要去问燕老师。下面我们跳过解析过程,来欣赏一下燕老师优美的暴力:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    #define N 604
    using namespace std;
    int n,T,t[N],val[N],x,y,lo,dp[N][N];
    int d[N],ans=0,dmax;
    bool mark[N];
    
    void dfs(int i,int time,int weight){
        if(time>T)return;
        if(weight+dp[1][T-time]<ans) //神仙的剪枝
            return;
        ans=max(ans,weight);
        for(int a=1;a<=n;a++){
            if(!mark[a]){
                mark[a]=true;
                dfs(i+1,time+t[a],weight+d[i]*val[a]);
                mark[a]=false;
            }
        }
    }
    int main(){
        cin>>n>>T;
        for(int a=1;a<=n;a++){
            cin>>x>>y>>t[a];
            val[a]=y*10+x;
        }
        for(int a=1;a<=n;a++)
            cin>>d[a],dmax=max(dmax,d[a]);
        for(int a=n;a>=1;a--){
            for(int b=0;b<t[a];b++)
                dp[a][b]=dp[a+1][b];
            for(int b=t[a];b<=T;b++)
                dp[a][b]=max(dp[a+1][b],dp[a+1][b-t[a]]+val[a]*dmax);
        }
        dfs(1,0,0);
        cout<<ans;
        return 0;
    }
    

    40 + 20pts

    (d_i = 1),相当于数列 (d) 不存在,这不就是 (01) 背包模板吗...

    照着“采药”那道题改一下即可。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 1001;
    int n, T, ans = 0, a[N], b[N], w[N], d[N], val[N], f[N][N];
    
    int main()
    {
        scanf("%d%d", &n, &T);
        swap(n, T);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%d", &a[i], &b[i], &w[i]);
            val[i] = b[i] * 10 + a[i];
            //cin >> w[i] >> val[i];
        }
        //for(int i = 1; i <= n; i++) d[i] = 1;
        for(int i = 1; i <= n; i++) scanf("%d", &d[i]);
        memset(f, 0, sizeof(f));
        for(int i = 1; i <= n; i++)
            for(int k = T; k >= w[i]; k--)
                for(int j = 1; j <= i; j++)
                    f[j][k] = max(f[j][k], f[j - 1][k - w[i]] + val[i] * d[j]),
                    ans = max(ans, f[j][k]);
        printf("%d", ans);
        return 0;
    }
    

    100pts

    普通的 (01) 背包好像不行了,可不可以在模板的基础上稍作改动呢?

    假设我们已经选择了 (i) 个物品 (s_1, s_2, s_3...s_i),这些物品的体积已经确定了,我们现在需要确定的是它们的排放顺序。有一个很显然的贪心,那就是用较大的 (d_i)较大的 (val_i) 相乘,以获得最大价值。

    考虑把这个过程融入到 (mathrm{dp}) 中:

    首先,由于物品的先后顺序不影响背包过程,先对物品按照 (val) 从大到小进行排序

    那么接下来可不可以对 (d) 排序,然后跑一个 (01) 背包呢?答案是不行。如果我们在 (n) 个物品中只选择了 (m) 个,而 (d_i) 的范围是 (1-n),这时候多出来的 (d_j) ((m<j leq n)) 将会干扰答案。

    所以我们考虑一个更为暴力的做法:枚举 (m),对前 (m)(d_i) 排序,再进行一个类似于多维背包(mathrm{dp}).

    具体来说,设 (f_{i,j}) 表示已经选了 (i) 个物品,体积为 (j) 的最大价值,(mathrm{dp}) 部分代码如下:

    for(int i = 1; i <= n; i++) //枚举物品
        for(int k = T; k >= s[i].w; k--) //枚举体积
            for(int j = 1; j <= min(num, i); j++) //枚举当前选的是第几个
                f[j][k] = max(f[j][k], f[j - 1][k - s[i].w] + s[i].val * g[j]); //g是排序后的d
    

    注意,最终我们需要的是恰好(m) 个物品的价值,所以背包需要初始化为极小值。时间复杂度 (mathrm{O(n^3T)})

    最后放上垃圾的代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define int long long
    using namespace std;
    
    const int N = 233333;
    struct node { int w, val; } s[N];
    int n, T, ans = 0, a[N], b[N], d[N], g[N], f[233][233];
    
    bool cmp(node a, node b) { return a.val > b.val; }
    bool cmp2(int a, int b) { return a > b; }
    
    signed main()
    {
        scanf("%lld%lld", &n, &T);
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld%lld%lld", &a[i], &b[i], &s[i].w);
            s[i].val = b[i] * 10 + a[i];
        }
        for(int i = 1; i <= n; i++) scanf("%lld", &d[i]);
        sort(s + 1, s + n + 1, cmp);
        for(int num = 1; num <= n; num++)
        {
            for(int i = 1; i <= num; i++) g[i] = d[i];
            sort(g + 1, g + num + 1, cmp2);
            memset(f, -0x7f7f7f, sizeof(f));
            f[0][0] = 0;
            for(int i = 1; i <= n; i++) 
                for(int k = T; k >= s[i].w; k--)
                    for(int j = 1; j <= min(num, i); j++)
                        f[j][k] = max(f[j][k], f[j - 1][k - s[i].w] + s[i].val * g[j]);
            for(int i = 0; i <= T; i++) ans = max(ans, f[num][i]);
        }
        printf("%lld", ans);
        return 0;
    }
    

    UPDATE

    好多大佬都在卡最优解啊...

    放一个卡常数版本的被吊打的标程吧

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define LL long long
    #define re register
    using namespace std;
    
    const int N = 233;
    struct node { LL w, val; } s[N], h[N];
    LL n, T, tot = 0, sum = 0, ans = 0, a[N], b[N], d[N], g[N], f[102][102]; //因为需要 memset,f数组开小一点
    
    bool cmp(node a, node b) { return a.val > b.val; }
    bool cmp2(LL a, LL b) { return a > b; }
    bool cmp3(node a, node b) { return a.w < b.w; }
    
    int main()
    {
        scanf("%lld%lld", &n, &T);
        for(re int i = 1; i <= n; i++)
        {
            scanf("%lld%lld%lld", &a[i], &b[i], &s[i].w);
            s[i].val = b[i] * 10 + a[i];
        }
        for(re int i = 1; i <= n; i++) h[i] = s[i];
        for(re int i = 1; i <= n; i++) scanf("%lld", &d[i]);
        sort(s + 1, s + n + 1, cmp);
        sort(h + 1, h + n + 1, cmp3); //数据很水,加这个优化快了很多QWQ
        for(re int i = 1; i <= n; i++) { if(sum + h[i].w > T) break; sum += h[i].w, tot++; }
        for(re int num = 1; num <= tot; num++)
        {
            g[num] = d[num]; //卡常数,避免重新复制一遍
            sort(g + 1, g + num + 1, cmp2);
            memset(f, -0x7f7f7f, sizeof(f));
            f[0][0] = 0;
            for(re int i = 1; i <= n; i++)
                for(re int k = T; k >= s[i].w; k--)
                    for(re int j = 1; j <= min(num, i); j++)
                        f[j][k] = max(f[j][k], f[j - 1][k - s[i].w] + s[i].val * g[j]);
            for(re int i = 0; i <= T; i++) ans = max(ans, f[num][i]);
        }
        printf("%lld", ans);
        return 0;
    }
    
  • 相关阅读:
    第一节
    20150506--不重复的随机(备忘)
    20150506—WinForm自动生成按钮&按钮拖动
    20150414---ListView简介(web)
    20150410---GridView分页(备忘)
    添加图片
    20150311—html中iframe(转发)
    20150323--MVC
    20150320--安全性,Membership类
    20150317--委托、代理
  • 原文地址:https://www.cnblogs.com/Andy-park/p/14070830.html
Copyright © 2020-2023  润新知