找到可能数量的有效方法

时间:2016-09-12 15:08:07

标签: c algorithm performance time

给定三个整数nkdn可以将多少种方式表示为正整数i<=k的总和,这样d 1}}在总和中至少出现一次。谨慎0<d<=k。我的方法是递归的;

#include <stdio.h>
#include <stdlib.h>
int n,k,d,ans=0;
void solve(int totw,int flag)//totw is the total sum till now, and flag is to keep track of the number of d's in the sum.
{
    if(totw>n)
        return;
    if(totw==n && flag>0)//flag>0--->at least 1 d
    {
        ans = (ans+1)%1000000007;//answer is expected modulo 10^9+7
        return;
    }
    int i=1,h=k;
    if(h>n-totw)
        h=n-totw;
    while(i<=h)
    {
        if(i==d)
            flag++;
        solve(totw+i,flag);
        i++;
    }
}
int main()
{
    scanf("%d %d %d",&n,&k,&d);
    solve(0,0);
    printf("%d",ans);
}

输入:
3 3 2
输出:
2
但法官显示Time Limit Exceeded。在这种情况下,还有更有效的算法吗? 0<n,k<=100
PS:我只是想知道是否有任何组合论证可以在没有recursioniteration的情况下解决这个问题。是的.... 总和顺序 重要

3 个答案:

答案 0 :(得分:1)

您可以将递归调用表示为图形。每个节点都由函数(totw, flag)的参数给出,并且每当进行递归调用时都会添加边。此图表中有大约n*2个节点和n*2*k边。

此图表有一个非常有趣的属性:它是DAG(例如因为totw在每次调用时严格增加)。因此,当您调用solve(totw, flag)时,可以将结果保存在数组中,这样就不会计算两次。

这实际上是一种解释dynamic programming的方法:一种在DAG中找到最短路径的算法(稍作修改,它也可以计算最长路径/路径数,但你明白了)。图G = (V, E)上的时间复杂度为O(|V|+|E|)(实际上是amortized analysis种类。)

因此,将结果保留在数组中将导致O(nk)时间复杂度。

实际上,您可以通过略微更改图表来缩短实施时间:

enum { UNKNOWN = -1 };

/* not tested */
/* solve(num, d_seen): returns the answer to the problem with a sum
   target, knowing whether `d` was already used */
int solve(int sum, int d_seen)
{
  if (sum == 0) return d_seen;
  int ans = dp[sum][d_seen];
  if (ans == UNKNOWN) {
    ans = 0;
    /* min(a, b) being what it is supposed to be */
    for (int i = 1; i <= min(sum, k); ++i)
      ans = (ans + solve(sum - i, d_seen || (i == d))) % MOD;
  }

  return (dp[sum][d_seen] = ans);
}

顺便说一下,在递增它之后你不会将flag重新置于0。

答案 1 :(得分:0)

参考本维基百科页面:Stars and Bars

基本上,将n分成k个正整数的方法的数量为(n-1)C(k-1)n-1选择k-1。因此,您可以将k = 1迭代到n-d-1,以查找可以将n-d-1拆分为任意数量的正整数的方式。

请参阅here,了解如何有效地为大型nCkn高效计算k

答案 2 :(得分:0)

当然,一些组合数学可以在不使用代码的情况下解决这个问题。但是递归挑战看起来很有趣。

经过轻微测试的例子 - 效率不比OP高。
(步骤1,使用更有意义的变量名称)

unsigned long solve(unsigned sum_n, unsigned max_value_k, unsigned special_d) {
  if (sum_n == 0) return special_d == 0;
  unsigned long solutions = 0;

  // Improve efficiency, only loop up to min(k,n)
  if (max_value_k > sum_n) {
    max_value_k = sum_n;
  }

  // Will the loop contain d?
  if (max_value_k >= special_d) {
    for (unsigned i = 1; i <= max_value_k; i++) {
      solutions += solve(sum_n - i, max_value_k,
          i == special_d ? 0 : special_d);
      solutions %= 1000000007;
    }
  }
  return solutions;
}

#include <stdio.h>
int main(void) {
  unsigned n, k, d;
  for (n=0; n<100; n++) {
    printf("%u %lu\n", n, solve(n, 100, 1));
    fflush(stdout);
  }
  scanf("%u %u %u", &n, &k, &d);
  printf("%lu\n", solve(n, k, d));
}