您的位置:首页 > 其它

pku acm 1833 排列

2012-04-11 16:27 288 查看
这道题目,最直观的想法是求出1到n的所有排列,然后将全部排列排序.

但是,n最大可以是1024,1024!个排 列,几乎永远也算不出来,算出来也没有地方存放。那么,有没有公式或规律,能够很快由一个排列推算出下k个排列呢?

实际上寻找规律或公式都是徒劳的,只能老老实实由给定排列算出下一个排列,再算出下一个排列……一直算到第k的排列。

鉴于k的值很小,最多只有64,因此这种算法应该是可行的。

如何由给定排列求下一个排列?

不妨自己动手做一下。比如:“2 1 4 7 6 3 5”的下一个排列是什么?

容易,显然是“2 1 4 7 6 5 3”,那么,再下一个排列是什么?有点难了,是“2 1 5 3 4 6 7”。

以从“2 1 4 7 6 5 3”求出下一个排列“2 1 5 3 4 6 7”作为例子,可以总结出求给定排列的下一个排列的步骤:

假设给定排列中的n个数从左到右是a1, a2, a3……an。

1)从an开始,往左边找,直到找到某个aj,满足aj-1<aj

(对上例j, 这个aj就是7, aj-1 就是4)。

2)在aj、aj+1…… an中找到最小的比aj-1大的数,将这个数和aj-1互换位置

(对上例, 这个数就是5,和4换完位置后的排列是“2 1 5 7 6 4 3”)。

3)将从位置j到位置n的所有数(共n-j+1个)从小到大重新排序,排好序后,新的排列就是所要求的排列。

(对上例,就是将“7 6 4 3”排序,排好后的新排列就是“2 1 5 3 4 6 7”)。

当然,按照题目要求,如果a1, a2, a3……an已经是降序,那么它的下一个排序就是an, an-1, an-2……a1。

注:上面的是基本思路,但是我们可以根据当前排列的性质,加以优化;

对于第一步,没法优化,只好从后到前找,找到了J。

第二布就可以优化了。循环找是可以,但是从J到J+1是倒序的,这样的话,根据有序的这个特性可以用二分查找,效率由O(n)提到到了O(log(n)).

第三部,排序仍是不需要的,因为从J到J+1是倒序的,只需反转就OK 了,降到了O(n).

接下来粘上代码以供参考

#include<stdio.h>
const int N=1015;
int str
;
int n,k;

void restr(int begin)
{
int end=n-1;
int p;
while(end>begin)
{
p=str[end];
str[end]=str[begin];
str[begin]=p;
end--;
begin++;
}

}

int find(int p,int begin,int end)
{
if(begin==end)return begin;
if(begin+1==end)
{
if(str[end]>str[p])return end;
return begin;
}

int mid=(begin+end)/2;
if(str[mid]<=str[p])return find(p,begin,mid-1);
return find(p,mid,end);
}

void serch(int p)
{
int p0=find(p-1,p,n-1);//find the first number that is bigger than the value of str[p-1]
int p1=str[p-1];
str[p-1]=str[p0];
str[p0]=p1;
}

void next()
{
int i=n-1;
while(i && str[i-1]>=str[i])i--;
if(i==0){restr(0);return ;}
serch(i);
restr(i);
}

void print()
{
printf("%d",str[0]);
for(int i=1;i<n;i++)
{
printf(" %d",str[i]);
}
printf("\n");
}

int main()
{

scanf("%d",&n);
while(scanf("%d%d",&n,&k)!=EOF)
{
for(int i=0;i<n;i++)scanf("%d",&str[i]);
if(n!=1)
{
while(k--)
{
next();
}
}

print();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: