所有子集中公共索引处的值之和

Sum of values at common indexes in all subsets?

本文关键字:索引 集中      更新时间:2023-10-16

在最近的一个问题中,我必须对大小为n的数组中大小为k的所有可能子集的公共索引的所有值求和。

For example: If

array ={1,2,3}

其子集(k=2)为(x [i], x [j]),其中i

1 2
1 3
2 3
Sum:4,8

首先,我使用递归(与生成所有子集的递归相同)

int sum_index[k]={0};
void sub_set(int array[],int n,int k,int temp[],int q=0,int r=0)
{
    if(q==k)
    {
       for(int i=0;i<k;i++)
          sum_index[i]+=temp[i];
    }
    else
    {
        for(int i=r;i<n;i++)
        {
            temp[q]=array[i];
            sub_set(value,n,k,temp,q+1,i+1);
        }
    }
}

问题是它花的时间比预期的要长。

然后我修改为…

void sub_set(int array[],int n,int k,int temp[],int q=0,int r=0)
{
    if(q==k)
    {
       return;
    }
    else
    {
        for(int i=r;i<n;i++)
        {
            temp[q]=array[i];
            sum_index[q]+=temp[q];  //or sum_index[q]+=s[i];
            sub_set(value,n,k,temp,q+1,i+1);
        }
    }
}

还是花了太多时间!!

这个问题还有别的办法吗?或者其他我不知道的修改??

与其在可能的子集中迭代,不如把它看作是一个组合问题。

要使用k=2和{1,2,3}的例子,让我们看看结果的第一个值。它有两个1和一个2。两个1对应于可以由{2,3}组成的第一个元素集,一个2对应于可以由{3}组成的第一个元素集的数量。对于结果的第二个元素中的一个2和两个3,也存在类似的安排,并查看在所考虑的元素之前出现的元素的子集。

当k>2时,事情变得有点复杂,因为这时你必须在考虑的元素之前和之后寻找元素组合的数量,但基本前提仍然有效。将之前的可能子集的数量乘以之后的子集的数量,这将告诉您每个元素对结果的贡献次数。

用O(n^2)代替O(n!)的解:
首先是一个小小的(:))解释,然后是一些代码:

我将在这里假设您的数组已排序(如果没有,则先使用std::sort)。此外,我将使用数组值1,2,3,4…在这里,如果数组包含任意值(如2 8 17),则必须将其视为索引(即。1=>2, 2=>8等)

定义:(x choose y)表示二项式系数,其计算方法也在链接中。如果数组大小为a,子集大小为k,则(a choose k)是排列的数量,例如。例如:(1,2),(1,3)和(2,3)。

如果您将排列顺序写在彼此下面,则需要每列的和,如果您知道每个数组元素在每列中出现的次数,则这将很容易。第一列有多少1 ',2 '和3 ',第二列有多少(k=2)。

这里有一个更大的例子来解释:(1,2,3,4,5)和所有可能的k´s(每个在一个块中):

1
2
3
4
5
12
13
14
15
23
24
25
34
35
45
123
124
125
134
135
145
234
235
245
345
... (didn´t write k=4)
12345

让我们引入列索引,0<=c<k,即。C =0表示第一列,C =1表示第二列,以此类推;数组大小为s=5

所以,看。在k=3块中,您将注意到以1开头的行(列c=0)具有k=2的所有值(2,3,4,5)的排列,更一般地说,列c中的值x在其后面具有x+1s的所有值排列。从x+1s的值是s-x个不同的值,在c列之后又多了k-c-1个列。因此,对于值x,您可以计算((s-x) choose (k-c-1))
另外,第一列只有值1、2、3,最后的两个数字不在这里,因为在这一列之后还有两个更多的列。

如果对第一列这样做,效果会很好。如。上面k=3的第一列值为1:
count(x) = ((s-x) choose (k-c-1)) = (4 choose 2) = 6
确实有6个1。为每个数组值计算这个计数,乘以x*count(x),并对每个x求和,这就是第一列的结果。

其他列稍微难一点,因为可能有多个相同数量的"排列块"。首先,上面的步骤需要做一个小的调整:您需要一个乘数数组,每个数组值对应一个乘数,开始时每个乘数为1。在上面的计算x*count(x)中,取x*count(x)*muliplier(x)代替。

k=3 -example中,第一列的1可以跟2、3、4,2可以跟3、4,3可以跟4。所以第二列的基于3的排列需要计算两次,基于4的排列需要计算三次;更一般地说,前面的列中有更小的值。与当前乘数相乘。

…一些代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// factorial (x!)
unsigned long long fact(unsigned char x)
{
    unsigned long long res = 1;
    while(x)
    {
        res *= x;
        x--;
    }
    return res;
}
//binomial coefficient (n choose k)
unsigned long long binom(unsigned char n, unsigned char k)
{
    if(!n || !k) return 1;
    return (fact(n) / fact(k)) / fact(n-k);
}
//just for convenience
template<class T> void printvector(std::vector<T> data)
{
    for(auto l : data) cout << l << " ";
    cout << endl;
}
std::vector<unsigned long long> calculate(std::vector<int> data, int k)
{
    std::vector<unsigned long long> res(k, 0); //result data
    std::vector<unsigned long long> multiplier(data.size(), 1);
    if(k < 1 || k > 255 || data.size() < 1) return res; //invalid stuff
    std::sort(data.begin(), data.end()); //as described
    for(int column = 0; column < k; column++) //each column separately
    {
        //count what to multiply to the multiplier array later
        std::vector<unsigned long long> newmultiplier(data.size(), 0);
        //for each array element in this column
        for(int x = column; x <= (data.size() + column - k); x++)
        {
            //core calculation
            res[column] += data[x] * multiplier[x] * binom(data.size() - x - 1, k - column - 1);
            //counting the new multiplier factor
            for(int helper = x + 1; helper < data.size(); helper++)
                newmultiplier[helper]++;
        }
        //calculating new multiplier
        for(int x = 0; x < data.size(); x++)
        {
            if(newmultiplier[x])
                multiplier[x] *= newmultiplier[x];
        }
    }
    return res;
}
int main() {
    printvector(calculate({1,2,3}, 2)); //output 4 8
    return 0;
}

std::next_permutation可能有所帮助:

std::vector<int> sub_set(const std::vector<int>& a, int k)
{
    std::vector<int> res(k, 0);
    std::vector<bool> p(a.size() - k, false);
    p.resize(a.size(), true);
    do
    {
        int index = 0;
        for (std::size_t i = 0; i != p.size(); ++i) {
            if (p[i]) {
                res[index++] += a[i];
            }
        }
    } while (std::next_permutation(p.begin(), p.end()));
    return res;
}

现场演示