编程竞赛中的背包变体

Posted

技术标签:

【中文标题】编程竞赛中的背包变体【英文标题】:Knapsack variation in Programming contest 【发布时间】:2014-05-18 06:36:13 【问题描述】:

问题:

考试由 N 个问题组成。 N道题的分数分别为m1、m2、m3、.. mN。 Jam 正在参加考试,他想最大限度地提高自己的分数。 然而,他需要一些时间来解决每个问题。他解决问题所用的时间分别为 t1, t2, t3, .. tN。 考试总时间为T。 但是 Jam 的老师很聪明,她知道 Jam 会想办法获得最高分。所以,为了迷惑Jam,她还为他提供了奖金—— 提议是,Jam 可以选择一个问题,他可以为该问题获得双倍的分数。 现在,Jam 确实很困惑。帮助他找出他可以获得的最大分数。

约束

1

1

1

1

我尝试了这个问题here 并提出了以下算法:

既然问题说,我们可以选择一个问题,他可以为该问题获得双倍的分数。

所以,为了选择那个问题,我应用了贪婪方法,即..

    应选择具有较大(分数/时间)比率的问题,他可以为此问题获得双倍分数。

    如果该比例也相同,则应选择分数较大的问题。

    就我理解的问题而言,其余的都是简单的背包。 我尝试了以下实现,但得到了错误的答案。 对于给定的测试用例,我的代码给出了正确的输出

    3 10 1 2 3 4 3 4

我已经尝试了 cmets 部分中给出的这个测试用例,我的代码给出了 16 作为输出,但答案应该是 18

  3 
  9
  9 6 5
  8 5 3

蛮力方法会超出时间限制,因为解决 N 个背包的复杂度 O(nW) 将给出 O(n^2 W) 的整体时间复杂度 我认为可以为这个问题开发一个更具体的算法。 还有比这更好的主意吗?

谢谢

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int T[2][10002];
    int a[1000002],b[100002];
    float c[100002];
    int knapSack(int W,int val[],int wgt[],int n)
    
    int i,j;

    for(i=0;i<n+1;i++)
    
        if(i%2==0)
        
            for(j=0;j<W+1;j++)
            
            if(i==0 || j==0)
            T[0][j]=0;
            else if(wgt[i-1]<=j)
            T[0][j]=max(val[i-1]+T[1][j-wgt[i-1]],T[1][j]);
            else
            T[0][j]=T[1][j];
            
        
        else
        
            for(j=0;j<W+1;j++)
            
            if(i==0 || j==0)
            T[1][j]=0;
            else if(wgt[i-1]<=j)
            T[1][j]=max(val[i-1]+T[0][j-wgt[i-1]],T[0][j]);
            else
            T[1][j]=T[0][j];
            
        
    
    if(n%2!=0)
        return T[1][W];
    else
        return T[0][W];
    


    int main()
    
    int n,i,j,index,t,mintime,maxval;
    float maxr;
    cin>>n;
    cin>>t;
    for(i=0;i<n;i++)
        cin>>a[i];

    for(i=0;i<n;i++)
        cin>>b[i];

    maxr=0;
    index=0;
    mintime=b[0];
    maxval=a[0];

    for(i=0;i<n;i++)
        
            c[i]=(float)a[i]/b[i];  
                if(c[i]==maxr && b[i]<=t)
                
                    if(a[i]>=maxval)
                    
                    maxval=a[i];
                    mintime=b[i];
                    index=i;
                    
                   
                else if(c[i]>maxr && b[i]<=t)
                
                maxr=c[i];
                maxval=a[i];
                mintime=b[i];
                index=i;
                

        

    a[index]=a[index]*2;
    int xx=knapSack(t,a,b,n);
    printf("%d\n",xx);
    

【问题讨论】:

当问题需要准确答案时,为什么要使用贪心近似? @user2357112:因为我想不出更好的方法 蛮力法是解N个背包(在每个背包中选择另一个问题进行加倍)并采取整体最佳解决方案。 @Henry:感谢您提出蛮力方法,但这肯定会超出时间限制 @user2357112:我已经知道暴力破解了。还有比这更好的吗? 【参考方案1】:

要解决这个问题,我们先来看the wikipedia article on the Knapsack problem,它为正则问题提供了动态规划解决方案:

// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for j from 0 to W do
  m[0, j] := 0
end for 
for i from 1 to n do
  for j from 0 to W do
    if w[i] <= j then
      m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
    else
      m[i, j] := m[i-1, j]
    end if
  end for
end for

(正如文章所说,您可以通过使用一维数组 m 而不是二维数组来减少内存使用量)。

现在我们可以使用这个想法来解决扩展问题。您可以计算两个表:您可以计算 m0 和 m1,而不是 m。 m0[i, w] 存储使用重量最多为 w 的前 i 个项目(在您的情况下是时间)获得的最大值,不使用双重评分问题。类似地,m1 存储使用权重(在您的情况下)最多为 w 的前 i 个项目获得的最大值,并使用双重评分问题。

更新规则改为:

if w[i] <= j then
    m0[i, j] := max(m0[i-1, j], m0[i-1, j-w[i]] + v[i])
    m1[i, j] := max(m1[i-1, j], m1[i-1, j-w[i]] + v[i], m0[i-1, j-w[i]] + 2 * v[i])
else
    m0[i, j] = m0[i-1, j]
    m1[i, j] = m1[i-1, j]
end if

与常规问题一样,您可以使用两个一维数组而不是两个二维数组来减少内存使用量。

【讨论】:

它没有回答问题,这是背包的变体,而不是“常规”背包 @amit 我认为您没有阅读完整的答案。我介绍了常规问题的解决方案,以便我可以将其扩展到双分项的问题。 是的,你隐藏得太好了。道歉,尝试更好地格式化它(答案的开头,其中很大一部分是普通背包......)-除此之外,很好的答案(+1)。 PS,我也在考虑同样的事情,但是使用额外的维度而不是 m0,m1,IMO 会更优雅。 谢谢@amit,我已经编辑了答案,以便更清楚为什么我首先查看常规问题。【参考方案2】:

为什么不使用动态编程?

http://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming

【讨论】:

以上是关于编程竞赛中的背包变体的主要内容,如果未能解决你的问题,请参考以下文章

会刷编程竞赛题的AlphaCode来了!

CSDN编程竞赛 ——— 第十期

CSDN编程竞赛 ——— 第六期

CSDN编程竞赛 ——— 第六期

CSDN编程竞赛 ——— 第二十一期

CSDN编程竞赛 ——— 第二十一期