问题

给出一组n个正整数,将它们分成k个 子集,然后最小化每个子集的和的平方和.例如,设集合为[1,2,3]和k 为2,则解为[1,2]和[3].第一个子集的和的平方为(1+2)^2=9,第二个子集的和的平方为3^2=9.总和为9+9=18,这是最小的.

样本输入

N=10,k=2 [63230795,3521578,37513838,37860789,30498450,29795141,41263743,5815341,19046274,20919844]->41895269854617569

N=10,k=5 [42566460,61080136,12375813,29881559,61767889,60645182,22105410,17262225,34309213,38950048] ->29098109328960071

制约因素

  • 1个≤N≤20
  • 110
  • 这组数字都是正数.您需要使用uint64_t进行算术运算.

我的代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <stdint.h>

bool used[20] = {0};
int n, m;
uint64_t arr[20], min = UINT64_MAX;
int find(int nset, uint64_t sum);
int subset(uint64_t subsum, int cur, int sum, int nset){
    if (cur == n){
        find(nset+1, sum+subsum*subsum);
        return 0;
    }
    subset(subsum, cur+1, sum, nset);
    if (!used[cur]){
        used[cur] = 1;
        subset(subsum+arr[cur], cur+1, sum, nset);
        used[cur] = 0;
    }
    return 0;
}
int find(int nset, uint64_t sum){
    if (sum >= min)
        return 0;
    if (nset == m-1){
        uint64_t setsum = 0;
        for (int i = 0; i < n; i++)
            if (!used[i])
                setsum += arr[i];
        sum += setsum*setsum;
        if (sum < min)
            min = sum;
        return 0;
    }else{
        subset(0, 0, sum, nset);
        return 0;
    }
}
int main(){
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%llu", &arr[i]);
    uint64_t z = 0;
    find(0, z);
    printf("%llu", min);
}

我的 idea 是使用残酷搜索,当当前解大于当前解,但错误时,用简单的剪枝计算一个子集和下一个子集的平方和.我丢了什么东西吗?感谢您的回复.

推荐答案

溶胶

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <stdint.h>

uint64_t befsq[10] = {0}, arr[20], min = UINT64_MAX, avg;
int n, m, len[10] = {0};
int find(int cur){
    uint64_t s = 0;
    for (int i = 0; i < m; i++) s += befsq[i]*befsq[i];
    if (s >= min) return 0;
    if (cur == n){
        min = s;
        return 0;
    }
    for (int i = 0; i < m; i++){
        if (befsq[i] > avg)
            continue;
        if (befsq[i]+arr[cur] > avg){
            if (befsq[i]+arr[cur]-avg > (avg-befsq[i]))
                continue;
        }
        len[i]++;
        befsq[i] += arr[cur];
        find(cur+1);
        befsq[i] -= arr[cur];
        len[i]--;
        if (!len[i]) return 0;
    }
}

int main(){
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%llu", &arr[i]), avg += arr[i];
    avg /= m;
    find(0);
    printf("%llu", min);
}

最后,我想出了解决办法.这个 idea 是试图将每一个元素分配到每个子集.类似地,用一些"切"来减少搜索树.这里的"割",是子集的和越接近平均值,最小值就越小.此外, case 的子集越多,最小值就越小.因此,一旦发现子集没有元素,函数就会直接返回.这些都是我的观察,我不确定是否所有类似的问题都是真的.希望有人能证实我的 idea 是正确的.谢谢.

C++相关问答推荐

为什么静态说明符为内联函数生成外部定义?

为什么下面的递归基本情况在C中不起作用?

sizeof结果是否依赖于字符串的声明?

exit(EXIT_FAILURE):Tcl C API类似功能

空指针的运行时强制转换

如何将字符串argv[]赋给C中的整型数组?

如何在C客户端应用程序的ClientHello消息中添加自定义扩展?

可以将C变量限制为特定的读/写速度吗?

如何在C中使printf不刷新标准输出?

从uint8_t*转换为char*可接受

如何使用空元素块声明指针数组

基于蝶数恰好有8个除数的事实的代码

强制GCC始终加载常量(即只读),即使启用了优化

如何在MSVC中使用intSafe.h函数?

Makefile无法将代码刷新到ATmega328p

GnuCobol 使用 double 类型的参数调用 C 函数

C/C++编译器可以在编译过程中通过按引用传递来优化按值传递吗?

快速准确计算double的小数指数

C语言程序流程解释

定义 int a = 0, b = a++, c = a++;在 C 中定义了行为吗?