康拓展开补充:逆康拓展开


前置知识:康拓展开
逆康拓展开可以求解 1 1 1 ~ n n n的全排列中,字典序第 x x x个的排列。

逆康拓展开

比如,在 1 1 1 ~ 5 5 5的全排列当中,要求按字典序第 107 107 107的排列。
因为在康拓展开时,单调递增的序列算出的结果是 0 0 0,所以要将原数减一。
首先先让 x / x/ x/ ( n − 1 ) ! (n-1)! (n1)!,也就是 106 / ( 5 − 1 ) ! = 106 / 4 ! = 106 / 24 = 4 … … 10 106/(5-1)!=106/4!=106/24=4……10 106/(51)!=106/4!=106/24=4……10,那么就算出有 4 4 4个值比第一个值小,所以第一个值就是 5 5 5
接着把上次剩下的余数拿来继续除 10 / ( 4 − 1 ) ! = 10 / 3 ! = 10 / 6 = 1 … … 4 10/(4-1)!=10/3!=10/6=1……4 10/(41)!=10/3!=10/6=1……4,只有一个数比第二个值小,那么第二个值就是 2 2 2
下一次, 4 / ( 3 − 1 ) ! = 4 / 2 ! = 4 / 2 = 2 4/(3-1)!=4/2!=4/2=2 4/(31)!=4/2!=4/2=2,有两个数比第三个值小,那么第三个值就是 4 4 4,这时候 2 2 2已经被排过了,现在还没有排的元素是 1 1 1 3 3 3 4 4 4,故第三大的是 4 4 4
接着, 0 / ( 2 − 1 ) ! = 0 / 1 = 0 0/(2-1)!=0/1=0 0/(21)!=0/1=0,那就是当前没拍过的元素中第 1 1 1大,所以是 1 1 1
最后就只剩一个 3 3 3,那么最后排完就是 52413 52413 52413
思想就是康拓展开公式的逆用。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,k;
int a[N];
int f[N]={1};
signed main(){
	n=read(),k=read()-1;
	for(int i=1;i<=n;i++)a[i]=i,f[i]=f[i-1]*i;
	string s="";
	for(int i=1;i<=n;i++){
		int t=k/f[n-i];
		print(a[t+1]),putchar(' ');
		for(int i=t+1;i<=n;i++)a[i]=a[i+1];
		k%=f[n-i];
	}
}

或者也可以用动态数组。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,k;
vector<int>a;
int f[N]={1};
signed main(){
	n=read(),k=read()-1;
	for(int i=1;i<=n;i++)a.push_back(i),f[i]=f[i-1]*i;
	string s="";
	for(int i=1;i<=n;i++){
		int t=k/f[n-i];
		print(a[t]),putchar(' ');
		a.erase(a.begin()+t);
		k%=f[n-i];
	}
}

例题

Cow Line S
这是一道康拓展开加逆康拓展开的板子题。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,m,k;
vector<int>a;
int g[N];
int f[N]={1};
int bit[N];
void update(int x,int p){
	while(x<=n){
		bit[x]+=p;
		x+=x&-x;
	}
}
int query(int x){
	int res=0;
	while(x){
		res+=bit[x];
		x-=x&-x;
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)f[i]=f[i-1]*i;
	while(m--){
		char op;
		cin>>op;
		if(op=='P'){
			cin>>k;
			k--;
			string s="";
			for(int i=1;i<=n;i++)a.push_back(i);
			for(int i=1;i<=n;i++){
				int t=k/f[n-i];
				cout<<a[t]<<' ';
				a.erase(a.begin()+t);
				k%=f[n-i];
			}
			cout<<'\n';
		}
		else{
			for(int i=1;i<=n;i++)cin>>g[n-i+1];
			int ans=0;
			for(int i=1;i<=n;i++){
				int res=query(g[i]-1);
				ans+=f[i-1]*res;
				update(g[i],1);
			}
			cout<<ans+1<<'\n';
			for(int i=1;i<=n;i++)update(g[i],-1);//记得清空树状数组
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值