C语言 最小总和的最优选择

dkqlctbz  于 2023-08-03  发布在  其他
关注(0)|答案(4)|浏览(128)
  • 这是竞争对手程序员手册中的一个问题:*

我们得到了k个产品在n天内的价格,并且我们希望每种产品只购买一次。但是,我们被允许在一天内最多购买一件产品。最低总价是多少?
| 0个|一个|二个|三个|四个|五个|六个|七个| 7 |
| --|--|--|--|--|--|--|--| ------------ |
| 六个|九个|五个|| 八个|九个|一个|六个| 6 |
| 八个|| 六个|二个|七个|五个|七个|二个| 2 |
| 五个|三个|九个|七个|三个|五个|一人| 四个| 4 |
最佳选择是:

  • 产品0在第3天价格为2,
  • 产品1在第1天价格为2,
  • 产品2在第6天的价格为1。

总共是5个。
解决方案:
我们要么在d日不购买任何产品,要么购买属于集合Sx产品。在后一种情况下,我们从集合S中删除x,并将x的价格添加到总价格中。
下面是本书中的代码:

#include <stdio.h>
#ifndef min
    #define min(a, b) ((a) < (b) ? (a) : (b))
#endif

int main()
{
    int price[3][8] = {{ 6, 9, 5, 2, 8, 9, 1, 6 },
                       { 8, 2, 6, 2, 7, 5, 7, 2 },
                       { 5, 3, 9, 7, 3, 5, 1, 4 }};
    int n = 8, k = 3;
    int total[1<<10][10];
    //Buy all products on day 0
    for (int x = 0; x < k; x++) {
        total[1<<x][0] = price[x][0];
    }

    for (int d = 1; d < n; d++) {
        for (int s = 0; s < (1<<k); s++) {
            total[s][d] = total[s][d-1];
            for (int x = 0; x < k; x++) {
                if (s & (1<<x)) {
                    total[s][d] = min(total[s][d], total[s ^ (1<<x)][d-1] + price[x][d]);
                    break;
                }
            }
        }
    }
    //Output    
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            printf("%d", total[i][j]);
        }
        printf("\n");
    }
}

字符串
这个问题限制了我们每天只买一种产品,但代码似乎根本没有解决这个问题(而且,我们在第一天买所有产品,这很好)。该产量正好是在该日可获得的每种产品的最小值[1,2,1]。我做错什么了?

vngu2lb8

vngu2lb81#

经过相当长的时间在调试器中,我能够使算法从书中的工作。可以说书中提供的片段完全被打破了。
最主要的编辑:
1.如果我们从 * 相邻 * 和更新更复杂的和,则我们将仅更新更复杂的和,也就是说,我们不从001或010的和更新111处的和。我们使用__builtin_popcount来查找当前设置的索引与我们试图更新的索引之间的差异。
1.我们将只更新更高的顺序集,如果足够的天已经过去了 * 前 * 集被填充。
我希望我没有在这里(再次)犯错误。如果我做了,请随时纠正我。这次我确实试着验证了多个输入,这似乎起作用了。
请注意,我使用了多个完全不必要的局部变量。我只是想要一些清晰度和可读性。
这本质上与书中的算法相同,但有一组必要的限制使其正确运行。如果没有这些限制,它会添加完全不兼容的东西,或者在错误的时间添加,最终无法工作。
算法确实解决了你在sol[xorIndex][dayIndex-1] + currentPrice部分每天只能购买1件商品的问题。被访问的sol部分在 * 前 * 天用项目 * 填充,* 不包括 * 我们正在添加的项目。

int optimalSelection(int products, int days, int prices[products][days]){
    int sol[1<<products][days];
    memset(sol, 0, sizeof(sol));
    for (int x = 0; x < products; x++) {
        sol[1<<x][0] = prices[x][0];
    }

    for (int dayIndex = 1; dayIndex < days; dayIndex++) {
        int allPossibleSetsCount = 1<<products;
        for (int setIndex = 0; setIndex < allPossibleSetsCount; setIndex++) {
            int currentMin = sol[setIndex][dayIndex-1];
            for (int productIndex = 0; productIndex < products; productIndex++) {
                if (setIndex&(1<<productIndex)) {
                    // this is the index of the set WITHOUT current product
                    int xorIndex = setIndex^(1<<productIndex);
                    if(__builtin_popcount(xorIndex) > dayIndex)
                        continue;

                    if (__builtin_popcount(setIndex ^ xorIndex) == 1){
                        // minimum for the prior day for the set excluding this product
                        int previousMin = sol[xorIndex][dayIndex-1];
                        // current price of the product
                        int currentPrice = prices[productIndex][dayIndex];
                        sol[setIndex][dayIndex] = currentMin == 0 ? previousMin + currentPrice : std::min(previousMin + currentPrice, currentMin);
                        currentMin = sol[setIndex][dayIndex];
                    }

                }
            }
        }
    }

    return sol[(1<<products)-1][days-1];
}

字符串

zxlwwiss

zxlwwiss2#

发布的算法具有n.k.2k的时间和空间复杂度,这似乎非常昂贵,并且可能导致堆栈溢出对于中等大小的集合。
此外,输出不是很有信息性,并且限制 * 每天最多一个产品 * 似乎无法执行。
下面是一个使用递归的替代方法,具有类似的时间复杂度nk,但内存占用要小得多:

#include <stdio.h>

enum { N = 8, K = 3 };

struct optim {
    const int (*price)[N];
    int bestsol[K];
    int bestprice;
};

void test(struct optim *p, int i, int set, int price, int *sol) {
    if (i >= K) {
        if (p->bestprice > price) {
            p->bestprice = price;
            for (int j = 0; j < K; j++) {
                p->bestsol[j] = sol[j];
            }
        }
    } else {
        for (int d = 0; d < N; d++) {
            if (set & (1 << d)) {
                continue;  // constaint: only 1 product per day
            }
            sol[i] = d;
            test(p, i + 1, set | (1 << d), price + p->price[i][d], sol);
        }
    }
}

int main() {
    int price[K][N] = { { 6, 9, 5, 2, 8, 9, 1, 6 },
                        { 8, 2, 6, 2, 7, 5, 7, 2 },
                        { 5, 3, 9, 7, 3, 5, 1, 4 } };
    struct optim data = { price, { 0, 1, 2 }, price[0][0] + price[1][1] + price[2][2] };
    int sol[K];

    test(&data, 0, 0, 0, sol);
    printf("price: %d, days: [", data.bestprice);
    for (int i = 0; i < K; i++) {
        printf(" %d", data.bestsol[i]);
    }
    printf(" ]\n");
    return 0;
}

字符串
输出:price: 5, days: [ 3 1 6 ]

aor9mmx1

aor9mmx13#

原来书中提供的解决方案是不完整的。为了使程序返回正确的结果,必须填充第一天的所有子集,但在书中,只有包含Map到2的幂的单个元素的子集,即索引1,2,4,etc of total[][]被填充,这使得其他子集具有0的值。这使得随后的每一天的计算取最小值,即0line 14 to 16中的代码

for (int x = 0; x < k; x++) {
        total[1<<x][0] = price[x][0];
    }

字符串
必须替换为:

for (int s = 0; s < (1 << k); s++) {
    for (int x = 0; x < k; x++) {
      if (s & (1 << x)) {
        total[s][0] = price[x][0];
      }
    }
  }


每天的最小总和将是包含所有元素的集合,即total[(1<<k)-1][index of day]。经过所有更改后,工作代码为:

#include <stdio.h>

#ifndef min
 #define min(a, b)((a) < (b) ? (a) : (b))
#endif

int main()
{
    int price[3][8] = {
        { 6, 9, 5, 2, 8, 9, 1, 6 },
        { 8, 2, 6, 2, 7, 5, 7, 2 },
        { 5, 3, 9, 7, 3, 5, 1, 4 }
    };
    int n = 8, k = 3;

    //Changed to scale with input
    int total[1 << k][n];

    //Buy all products on day 0
    //Changes here
    for (int s = 0; s < (1 << k); s++)
    {
        for (int x = 0; x < k; x++)
        {
            if (s &(1 << x))
            {
                total[s][0] = price[x][0];
            }
        }
    }

    for (int d = 1; d < n; d++)
    {
        for (int s = 0; s < (1 << k); s++)
        {
            total[s][d] = total[s][d - 1];
            for (int x = 0; x < k; x++)
            {
                if (s &(1 << x))
                {
                    total[s][d] = min(total[s][d], total[s ^ (1 << x)][d - 1] + price[x][d]);
                    break;
                }
            }
        }
    }

    //Output
    //Changes here    
    printf("%d", total[(1 << k) - 1][n - 1]);

}

bkhjykvo

bkhjykvo4#

这是正确答案
替换第一个代码块

for (int x = 0; x < k; x++) {
total[1<<x][0] = price[x][0];
}

字符串

for (int s = 0; s < (1<<k); s++) {
    // total[s][d] = total[s][d-1];
    for (int x = 0; x < k; x++) {
        if (s & (1<<x)) {
            total[s][0] = total[s ^ (1<<x)][0] + price[x][0];
            break;
        }
    }
}


并从书中提到的以下代码块中删除 break 条件。

相关问题