rq06金明的預算方案題解

古老的揹包題了,動態規劃的氣息濃郁。

在著名的揹包九講裏面有詳細的介紹,我先搬運工一下。

7有依賴的揹包問題

 7.1簡化的問題 這種揹包問題的物品間存在某種“依賴”的關係。也就是說,物品i依賴於物品j,表示若選物品i,則必須選物品j。爲了簡化起見,我們先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。

 7.2算法 這個問題由NOIP2006中“金明的預算方案”一題擴展而來。遵從該題的提法,將不依賴於別的物品的物品稱爲“主件”,依賴於某主件的物品稱爲“附件”。由這個問題的簡化條件可知所有的物品由若干主件和依賴於每個主件的一個附件集合組成。 按照揹包問題的一般思路,僅考慮一個主件和它的附件集合。可是,可用的策略非常多,包括:一個也不選,僅選擇主件,選擇主件後再選擇一個附件,選擇主件後再選擇兩個附件……無法用狀態轉移方程來表示如此多的策略。事實上,設有n個附件,則策略有2n+1個,爲指數級。 考慮到所有這些策略都是互斥的(也就是說,你只能選擇一種策略),所以一個主件和它的附件集合實際上對應於6中的一個物品組,每個選擇了主件又選擇了若干個附件的策略對應於這個物品組中的一個物品,其費用和價值都是這個策略中的物品的值的和。但僅僅是這一步轉化並不能給出一個好的算法,因爲物品組中的物品還是像原問題的策略一樣多。 再考慮對每組內的物品應用2.3中的優化。我們可以想到,對於第k個物品組中的物品,所有費用相同的物品只留一個價值最大的,不影響結果。所以,可以對主件k的“附件集合”先進行一次01揹包,得到費用依次爲0...V−Ck所有這些值時相應的最大價值Fk[0...V−Ck]。那麼,這個主件及它的附件集合相當於V−Ck+1個物品的物品組,其中費用爲v的物品的價值爲Fk[v−Ck]+Wk,v的取值範圍是Ck≤v≤V。 也就是說,原來指數級的策略中,有很多策略都是冗餘的,通過一次01揹包後,將主件k及其附件轉化爲V−Ck+1個物品的物品組,就可以直接應用6的算法解決問題了。 

7.3較一般的問題 更一般的問題是:依賴關係以圖論中“森林”3的形式給出。也就是說,主件的附件仍然可以具有自己的附件集合。限制只是每個物品最多隻依賴於一個物品(只有一個主件)且不出現循環依賴。解決這個問題仍然可以用將每個主件及其附件集合轉化爲物品組的方式。唯一不同的是,由於附件可能還有附件,就不能將每個附件都看作一個一般的01揹包中的物品了。若這個附件也有附件集合,則它必定要被先轉化爲物品組,然後用分組的揹包問題解出主件及其附件集合所對應的附件組中各個費用的附件所對應的價值。 事實上,這是一種樹形動態規劃,其特點是,在用動態規劃求每個父節點的屬性之前,需要對它的各個兒子的屬性進行一次動態規劃式的求值。這已經觸及到了“泛化物品”的思想。看完8後,你會發現這個“依賴關係樹”每一個子樹都等價於一件泛化物品,求某節點爲根的子樹對應的泛化物品相當於求其所有兒子的對應的泛化物品之和。 7.4小結 NOIP2006的那道揹包問題我做得很失敗,寫了上百行的代碼,卻一分未得。後來我通過思考發現通過引入“物品組”和“依賴”的概念可以加深對這題的理解,還可以解決它的推廣問題。用物品組的思想考慮那題中極其特殊的依賴關係:物品不能既作主件又作附件,每個主件最多有兩個附件,可以發現一個主件和它的兩個附件等價於一個由四個物品組成的物品組,這便揭示了問題的某種本質。 後來,我在《揹包問題九講》第一版中總結此事時說:“失敗不是什麼丟人的事情,從失敗中全無收穫纔是。”之後的NOIP2007的比賽中,我得了滿分。



上面是純搬運的,如果是初接觸動態規劃,是很有必要把揹包九講看一遍。

這道題目簡單的來說就是把附件考慮到主件裏面來,循環的時候只考慮主件,而主件存在不拿,拿主件,拿主件加附件1,拿主件加附件2,拿主件加附件1和2一共五種情況。如果之前能把輸入的數據處理好了,狀態轉移的部分幾乎和01揹包一樣。

#include<stdio.h>
#include<stdlib.h>
struct hey
{   int num;
    int w[6];
    int v[6];
    int flag;
       }a[67];
int max(int a,int b,int c)
{
    if(a>=b&&a>=c)return(a);
    if(b>=a&&b>=c)return(b);
    if(c>=a&&c>=b)return(c);
    }
main()
{   
    int i,m,n,q[67],v[67],p[67];
    int j=1,M,N,k,f[67][3207]={0};
    scanf("%d %d",&N,&M);
    N=N/10;
    for(i=1;i<=M;i++)
          scanf("%d %d %d",&v[i],&p[i],&q[i]);
    for(i=1;i<=M;i++)
       {
        v[i]=v[i]/10;
        if(q[i]==0)
           {
             a[j].num=i;
             a[j].w[1]=v[i]*p[i];
             a[j].v[1]=v[i];   
             a[j].flag=1;
             j++;
            } 
        }
    m=j-1;
    for(i=1;i<=M;i++)
        if(q[i]!=0)
        {
           j=1;
           while(a[j].num!=q[i])j++;
           if(j>m)continue;
           a[j].flag++;
           a[j].w[a[j].flag]=a[j].w[1]+v[i]*p[i];
           a[j].v[a[j].flag]=a[j].v[1]+v[i];
           if(a[j].flag==3)
           {
              a[j].w[4]=a[j].w[2]+a[j].w[3]-a[j].w[1];
              a[j].v[4]=a[j].v[2]+a[j].v[3]-a[j].v[1];
              a[j].flag++;
           }
        }
    for(i=1;i<=m;i++)
       for(k=1;k<=a[i].flag;k++)
         for(j=N;j>=0;j--)
           if(j-a[i].v[k]>=0)
                f[i][j]=max(f[i-1][j-a[i].v[k]]+a[i].w[k],f[i-1][j],f[i][j]);
           else f[i][j]=max(0,f[i][j],f[i-1][j]);
     printf("%d\n",f[m][N]*10);    
               
      }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章