排列组合算法求助

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

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

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


...全文
341 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;
}
1、 FACTORY —追 MM 少不了请吃饭了, 麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西, 虽然口味有所不同, 但不管你带 MM 去麦当劳或肯德基, 只管向服务员说“来四个鸡翅”就行 了。麦当劳和肯德基就是生产鸡翅的 Factory 工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消 工厂模式 费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如 何创建及如何向客户端提供。 2、BUILDER — MM 最爱听的就是“我爱你”这句话了,见到不同地方的 MM,要能够用她们的 、 方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到 MM 我只要按对应的键, 它就能够用相应的语言说出“我爱你”这句话了, 国外的 MM 也可以轻松 搞掂,这就是我的“我爱你”builder。 (这一定比美军在 伊拉克用的翻译机好卖) 建造模式: 从而使一个建造过程生成具有不 建造模式 将产品的内部表象和产品的生成过程分割开来, 同的内部表象的产品对象。 建造模式使得产品内部表象可以独立的变化, 客户不必知道产品 内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。 3、FACTORY METHOD —请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住 、 是一件烦人的事情,我一般采用 Factory Method 模式,带着 MM 到服务员那儿,说“要一个 汉堡”,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。 工厂方法模式: 而是将具体创建的工作交给子类去做, 工厂方法模式 核心工厂类不再负责所有产品的创建, 成为一个抽象工厂角色, 仅负责给出具体工厂类必须实现的接口, 而不接触哪一个产品类应 当被实例化这种细节。 4、 、 PROTOTYPE —跟 MM 用 QQ 聊天, 一定要说些深情的话语了, 我搜集了好多肉麻的情话, 需要时只要 copy 出来放到 QQ 里面就行了, 这就是我的情话 prototype 了。 (100 块钱一份, 你要不要) 原始模型模式: 原始模型模式 通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原 型对象的方法创建出更多同类型的对象。 原始模型模式允许动态的增加或减少产品类, 产品 类不需要非得有任何事先确定的等级结构, 原始模型模式适用于任何的等级结构。 缺点是每 一个类都必须配备一个克隆方法。 5、 、 SINGLETON —俺有 6 个漂亮的老婆, 她们的老公都是我, 我就是我们家里的老公 Sigleton, 她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事) 单例模式: 而且自行实例化并向整个系统提供这个实 单例模式 单例模式确保某一个类只有一个实例, 例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。 结构型模式 6、ADAPTER —在朋友聚会上碰到了一个美女 Sarah,从香港来的,可我不会说粤语,她不 、 会说普通话,只好求助于我的朋友 kent 了,他作为我和 Sarah 之间的 Adapter,让我和 Sarah 可以相互交谈了(也不知道他会不会耍我) 适配器模式: 从而使原本因接口原因不 适配器模式 把一个类的接口变换成客户端所期待的另一种接口, 匹配而无法一起工作的两个类能够一起工作。 适配类可以根据参数返还一个合适的实例给客 户端。 7、BRIDGE —早上碰到 MM,要说早上好,晚上碰到 MM,要说晚上好;碰到 MM 穿了件新 、 衣服, 要说你的衣服好漂亮哦, 碰到 MM 新做的发型, 要说你的头发好漂亮哦。 不要问我“早 上碰到 MM 新做了个发型怎么说”这种问题,自己用 BRIDGE 组合一下不就行了 桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关 桥梁模式 联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是 继承关系,从而使两者可以独立的变化。 8、COMPOSITE —Mary 今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店, 、 你自己挑。”“这件 T 恤挺漂亮,买,这条裙子好看,买,这个包也不错, 买。”“喂,买了 三件了呀,我只答应送一件礼物的哦。”“什么呀,T 恤加裙子加包包,正好配成一套呀,小 姐,麻烦你包起来。”“……”,MM 都会用 Composite 模式了,你会了没有? 合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就 合成模式 是一个处理对象的树结构的模式。 合成模式把部分与整体的

69,370

社区成员

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

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