全排列 与m中挑出n个数

生成全排列的方法在网上有好多种,m中选出n个数的方法也有很多,多数是采用递归的方式,但实际上这两种问题都可以归结为同一个问题:m个数中可能出现的所有可能数。例如:1 ,2可以出现的所有数有:1,2,12,21

对所有可能出现的数的长度加以限制就可以变成生成全排列的问题或者m中选出n个数的排列组合问题


java的实现代码如下:

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class test {
	static int limitMin;
	static int limitMax;//挑出长度小于等于max且大于min的字符串排列组合
	static void func(List mylist,String prefix)
	{
		if(prefix.length()>=limitMin&&prefix.length()<=limitMax)
		System.out.println(prefix);//只有满足长度要求才输出,
		
		for(int i=0;i<mylist.size();i++)
		{
			List temp=new LinkedList (mylist);//
			func(temp,prefix+temp.remove(i));//将第i个数加到已处理的字符串后
		}
	}
//……MAIN函数在此
}


在main函数中设定limitMax和LimitMin的值,设定为总长度就为全排列问题

public static void main(String[] args) {
		// TODO Auto-generated method stub
		String []arry={"1","2","3","4","5"};
		limitMin=5;//限制最小长度
		limitMax=5;//限制最大长度
		func(Arrays.asList(arry),"");
	}

运行结果:



设定为全为3,则为5个数中选出3个数的排列组合问题

limitMax=3;

limitMin=3;

 运行结果:
 

## 题意 给定一个$1$到$n$的全排列,求一个全排列,使得其逆序对个数全排列相等,字典序大于原全排列且尽可能小 ## 思路 根据题意,我们可以知道要动顺序的数一定在一个右端点为$n$的区间中,那么我们可以枚举要动的区间的左端点,通过瞪眼法,我们看出,左端点即为从右往左数第一个数满足其右边至少有一个数比它大且其右边的逆序对个数不为$0$,然后我们就可以把此区间中逆序对个数求出,把区间中的数从小到大排列然后去构造逆序对。 ## 实现 求逆序对个数可以用树状数组维护(单点修改区间查询一个数右边比它小的数的个数)。为了让字典序尽可能小,把左端点数值区间中比它大的最小的数互换,把左端点踢出去,然后把区间中数从小到大排列,把最大的那些数反过来,这样我们就得到了很多逆序对。最后一个比较大的数放在这坨降序排列的数前面,这样就可以使得逆序对个数相同的情况下字典序最小(你问我比原来的字典序小怎么办,其实最开始我们就把左端点变大之后踢了出去,所以不用管它),然后就做完了,嗯,对。 ## 代码 ``` #include <bits/stdc++.h> using namespace std; const int N = 5e5 + 10; int n; int tree[N], input[N]; long long num[N]; int lowbit(int x){ return x & -x; } void add(int x){//单点修改 while(x <= n){ tree[x]++; x += lowbit(x); } return; } int cha(int x){//区间查询 int ans = 0; while(x){ ans += tree[x]; x -= lowbit(x); } return ans; } int read(){//快读 int s = 0; char ch = getchar(); while(ch > '9' || ch < '0') ch = getchar(); while(ch <= '9' && ch >= '0'){ s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar(); } return s; } int main() { cin >> n; for(int i = 1; i <= n; i++){//读入原序列 input[i] = read(); } for(int i = n; i >= 1; i--){//求每个点右侧比它小的数的个数 num[i] = cha(input[i]); add(input[i]); } int l = 0; int ma = 0; long long now = 0; for(int i = n; i >= 1; i--){//枚举左端点位置 if(input[i] < ma && now){ l = i; break; } now += num[i]; ma = max(ma, input[i]); } sort(input + l + 1, input + n + 1); //将数还原为初始状态(注意左端点不能排进去) int zz = l + 1; while(zz <= n && input[zz] < input[l]) zz++;//找到此时左端点应被修改为的数 swap(input[zz], input[l]); long long ge = 1; while(ge * (ge + 1) < now * 2) ge++;//求要被修改的末尾的数的个数 int shu = now - (ge - 1) * ge / 2 - 1;//求还差多少个逆序对没被满足 l = n - ge + 1; int r = n; if(shu <= 0){ while(l < r){//将升序改为降序以尽可能用最少的数构造最多的逆序对个数 swap(input[l], input[r]); l++; r--; } } else{ ge++; l = n - ge + 1; int ls = input[l + shu];//交换点使其逆序对个数满足条件 for(int i = l + shu - 1; i >= l; i--){ input[i + 1] = input[i]; } input[l] = ls; l++; while(l < r){//66行同理 swap(input[l], input[r]); l++; r--; } } for(int i = 1; i <= n; i++){//输出结果 cout << input[i] << " "; } return 0; } ``` 以上是我写的题解,请修改,使得其逻辑更清晰,思路更清楚
最新发布
11-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值