複数のナップザックは、n [i]をバイナリ分割することによって得られる数が0〜n [i]を形成できることを証明しています

バイナリスプリットとは何ですか?

 数nは、それよりも小さい2つの累乗すべてと残りの数の合計(指数関数的に増加)に分割されます。実際、この数値の2進数の各ビットは、0〜nの範囲の数値を形成するように選択することも選択しないこともできます。

  例:13は2 ^ 0、2 ^ 1、2 ^ 2、および6(6は残りの数)に分割できます。つまり、1 2 4 6に分割し、これらの4つの数を選択することで任意の数を表すことができます。 0から13までの番号。

例:
0:なしを選択します。
1:1を選択;
2:2を選択;
3:1と2を選択;
4:4を選択;
5:1と4を
選択; 6:2と4を
選択; 7:1と2と4を
選択; 8:2と4を選択6;
9:1と2と6を
選択; 10:4と6を
選択; 11:1と4と6を
選択; 12:2と4と6を
選択; 13:すべてを選択;

以下は個人的な証明です。ここで、nはn [i]を表すために直接使用されます。

証明:

①0〜2 ^ k-1(2 ^(k + 1)-1 <= n)が表現できることを証明する

べき級数(等比数列)の合計から、次のことがわかります。

2 ^ 0 + 2 ^ 1 + 2 ^ 2 +……+ 2 ^ k = 2 ^(k + 1)-1
2 ^ 0 + 2 ^ 1 + 2 ^ 2 +……+ 2 ^ k <2 ^( k + 1)

したがって、k + 1の2進数を少なくとも2のk乗に分割することができ(指数関数的に増加)、kビットの2進数がこれらの分割された数ですでに表されていることを取得することは難しくありません。
したがって、0〜2 ^(k + 1)-1(2 ^(k + 1)-1 <= n <2 ^(k + 2)-1)の10進数を表すことができます。

②次の証明2 ^ k〜nはこれらの数で表すことができます

明らかに、分割によって得られたすべての数はnを形成でき、n
を構成する数は1で減算され(2 ^ 0を除いて、他のすべてが選択されます)、
nを構成する数はn-1から2を引いたものです( 2を除く^ 1を選択しない場合は、他のすべてを選択します)、
nから3を引いた数で構成されるn-2の数を取得できます(2 ^ 0と2 ^が選択されていない場合を除き、他のすべてが選択されます)。取得N-3
............... .........
........................
..................
NXについて存在する
  2 ^(K + 1)-1 <= NX <2 ^(K + 2) -1
  2 ^(k + 1)-1 <= n <2 ^(k + 2)-1

上記の方程式を組み合わせると、次のようになります。0<= x <2 ^(k + 1)、つまり0 <= x <= 2(k + 1)-1;
①の結論により、次の数を得ることができます。これらのサイードによって分割されます。
 つまり、最初にすべての数値(2 ^ 0、2 ^ 1、2 ^ 2 +……+ 2 ^ kと分割後の残りの数値)を選択し、次にnを表すことができ、次にnxをInallで表すことできます。数の場合、Xを構成する数を移動してxの数を表すことにより、nxを取得できます。

したがって、命題は証明されます

アプリケーション:バイナリ分割を完全なバックパックで使用して、時間の複雑さを軽減できます.1〜nの列挙が変更されて分割数が列挙され、時間の複雑さがO(n)からo(logn)に削減されます。)

郵便番号

非関数呼び出し

#include <iostream>
using namespace std;
const int N = 110;
int n,V;
int w[N];
int v[N];
int s[N];
int dp[N];
int max(int i,int j){
    
    
    return i > j?i:j;
}

int main(){
    
    
    cin >> n >> V;

    for(int i = 1;i <= n;++i)
        cin >> v[i] >> w[i] >> s[i];
        
        for(int i = 1;i <= n;i++){
    
    
            if(s[i]*v[i]>=V){
    
    //但物品个数足够装满整个背包时,使用完全背包更快
                for(int j = v[i];j <= V;j++){
    
    
                    dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
                }
            }
            else//物品个数不够,则用01背包 因为物品个数不够装满整个包 所以 s[i] < V/v[i]
            for(int key = 1;s[i] > 0;s[i]-=key,key*=2){
    
    //选择key个 每次Key 个i物品进行01背包
                if(s[i]>=key){
    
    
                    for(int j = V;j >= key*v[i];j--){
    
    
                        dp[j] = max(dp[j],dp[j-key*v[i]] + key*w[i]);
                    }
                }else//二进制拆分剩下的数
                    for(int j =V;j >= s[i]*v[i];j--)
                        dp[j] = max(dp[j],dp[j-s[i]*v[i]] + s[i]*w[i]);
                
            }
        }
    int result = 0;
    for(int j = 0;j <= V;j++){
    
    
        result < dp[j] ? result = dp[j]:result = result;
    }
    cout << result;
    return 0;
}

関数を呼び出す

#include <iostream>
using namespace std;
const int N = 110;
int n,V;
int w[N];
int v[N];
int s[N];
int dp[N];
int max(int i,int j){
    
    
    return i > j?i:j;
}
void zeroonepack(int i){
    
    

    for(int key =1;s[i]>0;s[i]-=key,key<<=1){
    
    
        if(s[i]>=key)
            for(int j = V;j >= key*v[i];j--)
                dp[j] = max(dp[j],dp[j - v[i]*key] + w[i] *key);
        else
            for(int j = V;j >= s[i]*v[i];j--)
                dp[j] = max(dp[j],dp[j - v[i]*s[i]] + w[i] *s[i]);
    }
}
void completpack(int i){
    
    
    for(int j = v[i];j <= V;j++)
        dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
int main(){
    
    
    cin >> n >> V;

    for(int i = 1;i <= n;++i)
        cin >> v[i] >> w[i] >> s[i];
        
        for(int i = 1;i <= n;i++){
    
    
            if(s[i]*v[i]>=V){
    
    //物品个数不受限
                completpack(i);
            }
            else{
    
    //s[i] <V/v[i]; 物品个数受限
               zeroonepack(i);
            }
                
        }
        
    int result = 0;
    for(int j = 0;j <= V;j++){
    
    
        result < dp[j] ? result = dp[j]:result = result;
    }
    cout << result;
    return 0;
}

おすすめ

転載: blog.csdn.net/RunningBeef/article/details/111031018