排列组合算法求助

狼异族 2013-07-30 10:25:19
网上看的,题目是从连续的整数1~n中选取m个数字进行排列,排列规则如下:
1、如果第i个位置的数字为x,则第i+1个位置的数字必须大于等于2x。

问题:给出对应的n和m的值,问存在多少个排列方式?

我想的算法我就不好意思说了,时间复杂度都O(n^m)了,问下有什么好的算法么?或者这有公式么?


...全文
342 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
赵4老师 2013-08-02
  • 打赏
  • 举报
回复
题目应改为: 从连续的整数1~n中选取m个数进行排列,排列规则为: 如果第i个位置的数为x,则第i+1个位置的数必须大于等于2x。 给出对应的n和m的值,问存在多少种符合规则的排列方式?
FancyMouse 2013-08-02
  • 打赏
  • 举报
回复
原来是pongo上的。我最后写了一个(logn)^4的做法,差点TLE。我稍微cheat了一下,几个和贝努利数列相关的系数我直接把数值给hardcode进去了wwww
狼异族 2013-08-01
  • 打赏
  • 举报
回复
引用 17 楼 zhao4zhong1 的回复:
数字和数是一回事吗?
这个是1~n,而且如果第i个位置为x,则第i+1个位置的数字 >= 2*x
赵4老师 2013-07-31
  • 打赏
  • 举报
回复
数字和数是一回事吗?
一根烂笔头 2013-07-31
  • 打赏
  • 举报
回复
楼上大牛缩小范围到0-9有意思吗
赵4老师 2013-07-31
  • 打赏
  • 举报
回复
仅供参考
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
int i;
int L;
void main(int argc,char **argv) {
    if (argc<2) {
        printf("%s 要排列的字符串\n",argv[0]);
        return;
    }
    L=strlen(argv[1]);
    vector<char> P(L);
    vector<char>::iterator b,e,it;
    b=P.begin();
    e=P.end();
    for (i=0;i<L;i++) P[i]=argv[1][i];
    sort(b,e);
    i=0;
    do {
        printf("%10d: ",++i);
        for(it=b;it!=e;it++) printf("%c",*it);
        printf("\n");
    } while (next_permutation(b,e));
    printf("The total: %d",i);
}
赵4老师 2013-07-31
  • 打赏
  • 举报
回复
提醒: 因为数字范围为0~9 所以
第i个位置的数字为x,则第i+1个位置的数字必须大于等于2x。

0                 ,              0 1 2 3 4 5 6 7 8 9
1                 ,                  2 3 4 5 6 7 8 9
2                 ,                      4 5 6 7 8 9
3                 ,                          6 7 8 9
4                 ,                              8 9
5                 ,
6                 ,
7                 ,
8                 ,
9                 ,
FancyMouse 2013-07-30
  • 打赏
  • 举报
回复
这不是显然有O(n^2*m)的dp么……当然不算大数运算本身的复杂度的情况下。 dp的同时计算dp数组的prefix sum可以把复杂度降到O(n*m)。 再注意到第m-i步至多到n/2^i,这个剪枝可以再把m给做掉,只剩下O(n)了
IT民工TI 2013-07-30
  • 打赏
  • 举报
回复
设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
ri_aje 2013-07-30
  • 打赏
  • 举报
回复
感觉这题有公式,但是什么不知道。这阵儿太忙,估计没时间推这个了。
狼异族 2013-07-30
  • 打赏
  • 举报
回复
引用 1 楼 adlay 的回复:
我只想到回溯法 然后加一些判断来剪去无效的分支, 比如第一个数只能是小于 n - m 的.
其实第一个数只能是小于或者等于 n>>(m-1) 的数字的,就像你说用回溯的话,基本不现实,时间复杂度太大了。
www_adintr_com 2013-07-30
  • 打赏
  • 举报
回复
我只想到回溯法 然后加一些判断来剪去无效的分支, 比如第一个数只能是小于 n - m 的.
一根烂笔头 2013-07-30
  • 打赏
  • 举报
回复
来点理论 我正在尝试动态规划的实现
FancyMouse 2013-07-30
  • 打赏
  • 举报
回复
引用 9 楼 L812234929 的回复:
[quote=引用 7 楼 FancyMouse 的回复:] 很可惜。我说的动态规划和你写的这个完全不是一回事。不懂什么是dp就别乱总结。
果然这个东西很牛逼,一下子就快多了,不过要增加接近n的空间开销。[/quote] 大O意义下空间复杂度不变。所以这点不是优化的主要目标。等其他地方都没法优化了再来看这里。 这题可能有关于m和logn多项式的办法。不过这就要把细节想得很清楚了
IT民工TI 2013-07-30
  • 打赏
  • 举报
回复
引用 10 楼 L812234929 的回复:
[quote=引用 8 楼 jiujiujiuchun 的回复:] [quote=引用 4 楼 jiujiujiuchun 的回复:] 设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
补上代码

#define N 10  //从[1, N]中取数
#define M 3   //从[1, N]中取M个数
int a[M] = {0}; //保存临时序列,用于输出
int count = 0;  //取了多少个数字

//从[s, n]中取m个数
void Permutation(int n, int m, int s)
{
   int i;
   if(0 == m)
   {
      total++;
      printf("排列%d:\t",total);
      for(i=0; i<M; i++)
      {printf("%d ", a[i]);} //输出序列
      printf("\n");
      return;
    }
    int temp = 1 <<(m-1);
    int maxval = n/temp;
    for(i=s; i<=maxval; i++)
    {
        a[count++] = i;
        Permutation(n, m-1, i*2);
        count--;
    }
} 
[/quote] 你把N值改到1000或者更大再试下,把m值在适当的调整一下,就可以看出来了缺陷了。[/quote] 你难道没发现我两的算法本质是一致的!要说有问题,两者都存在的,只不过我的上限要比你小而已.
狼异族 2013-07-30
  • 打赏
  • 举报
回复
引用 8 楼 jiujiujiuchun 的回复:
[quote=引用 4 楼 jiujiujiuchun 的回复:] 设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
补上代码

#define N 10  //从[1, N]中取数
#define M 3   //从[1, N]中取M个数
int a[M] = {0}; //保存临时序列,用于输出
int count = 0;  //取了多少个数字

//从[s, n]中取m个数
void Permutation(int n, int m, int s)
{
   int i;
   if(0 == m)
   {
      total++;
      printf("排列%d:\t",total);
      for(i=0; i<M; i++)
      {printf("%d ", a[i]);} //输出序列
      printf("\n");
      return;
    }
    int temp = 1 <<(m-1);
    int maxval = n/temp;
    for(i=s; i<=maxval; i++)
    {
        a[count++] = i;
        Permutation(n, m-1, i*2);
        count--;
    }
} 
[/quote] 你把N值改到1000或者更大再试下,把m值在适当的调整一下,就可以看出来了缺陷了。
狼异族 2013-07-30
  • 打赏
  • 举报
回复
引用 7 楼 FancyMouse 的回复:
很可惜。我说的动态规划和你写的这个完全不是一回事。不懂什么是dp就别乱总结。
果然这个东西很牛逼,一下子就快多了,不过要增加接近n的空间开销。
IT民工TI 2013-07-30
  • 打赏
  • 举报
回复
引用 4 楼 jiujiujiuchun 的回复:
设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
补上代码

#define N 10  //从[1, N]中取数
#define M 3   //从[1, N]中取M个数
int a[M] = {0}; //保存临时序列,用于输出
int count = 0;  //取了多少个数字

//从[s, n]中取m个数
void Permutation(int n, int m, int s)
{
   int i;
   if(0 == m)
   {
      total++;
      printf("排列%d:\t",total);
      for(i=0; i<M; i++)
      {printf("%d ", a[i]);} //输出序列
      printf("\n");
      return;
    }
    int temp = 1 <<(m-1);
    int maxval = n/temp;
    for(i=s; i<=maxval; i++)
    {
        a[count++] = i;
        Permutation(n, m-1, i*2);
        count--;
    }
} 
FancyMouse 2013-07-30
  • 打赏
  • 举报
回复
引用 6 楼 L812234929 的回复:
[quote=引用 4 楼 jiujiujiuchun 的回复:] 设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
引用 5 楼 FancyMouse 的回复:
这不是显然有O(n^2*m)的dp么……当然不算大数运算本身的复杂度的情况下。 dp的同时计算dp数组的prefix sum可以把复杂度降到O(n*m)。 再注意到第m-i步至多到n/2^i,这个剪枝可以再把m给做掉,只剩下O(n)了
你们俩位的说法基本可以归纳到下面这个算法里面,这个算法没有处理数据位溢出的问题,先不管这个。这个算法的时间复杂度依然还是接近O(n^m)。
//这个算法的大致意思是先循环第一位可能的数字,再根据第一位的数字循环第二位可能出现的数字,以此类推。
__int64 Func(int n, int m, int iMinNum)
{
	if (m == 1)
	{
		return (__int64)(n - iMinNum + 1);
	}

	__int64 iRet = 0;
	int i = 0;
	for (i = iMinNum; i <= n/(1 << (m-1)); i++)
	{
		iRet += Func(n, m-1, i*2);
	}
	
	return iRet;
}
[/quote] 很可惜。我说的动态规划和你写的这个完全不是一回事。不懂什么是dp就别乱总结。
狼异族 2013-07-30
  • 打赏
  • 举报
回复
引用 4 楼 jiujiujiuchun 的回复:
设通项为Am,则有Am>=2*Am-1>=2*Am-2...>=2*A2>=2*A1 (注 m-1, m-2 是A的下标) 而Am<=n,所以有 A1<= n/(2^(m-1)),A2<=n/(2^(m-2))..... Am<=n/(2^(m-m)) 从而得到Am的取值范围[2*Am-1, n/(2^(m-m))] 把取值从A1到Am遍历就得到所有排列了!
引用 5 楼 FancyMouse 的回复:
这不是显然有O(n^2*m)的dp么……当然不算大数运算本身的复杂度的情况下。 dp的同时计算dp数组的prefix sum可以把复杂度降到O(n*m)。 再注意到第m-i步至多到n/2^i,这个剪枝可以再把m给做掉,只剩下O(n)了
你们俩位的说法基本可以归纳到下面这个算法里面,这个算法没有处理数据位溢出的问题,先不管这个。这个算法的时间复杂度依然还是接近O(n^m)。
//这个算法的大致意思是先循环第一位可能的数字,再根据第一位的数字循环第二位可能出现的数字,以此类推。
__int64 Func(int n, int m, int iMinNum)
{
	if (m == 1)
	{
		return (__int64)(n - iMinNum + 1);
	}

	__int64 iRet = 0;
	int i = 0;
	for (i = iMinNum; i <= n/(1 << (m-1)); i++)
	{
		iRet += Func(n, m-1, i*2);
	}
	
	return iRet;
}

69,373

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧