Did You AK Today? (今天你AK了吗?)

考虑到本文读者年龄原因,本文改为使用简体中文撰写。

题目描述

今有正整数 n,kn,k,求 1n1-nnn 个数的全排列,按字典序的第 kk 个。
数据满足 1n105,1kmin(n!,1020 000).1\leq n\leq10^5,1\leq k\leq\min(n!,10^{20\ 000}).

Solution\text{Solution}

请注意 kk 的上限! ls忘了打。这很重要!

对于 50%50\% 及更低的数据,ls已经解释得很详尽,在此无需赘述。

尝试使用 DeCantor Expansion(逆康托展开)算法求解。这篇文章有详细解释,看不懂找 ls 他英语一级棒。时间复杂度 O(n2)O(n^2)
预处理 1n1-n 的阶乘,时间复杂度 O(n2)O(n^2)
朴素的高精度除法,时间复杂度 O(n2)O(n^2)

综上所述,总的时间复杂度为 O(n2)O(n^2)。贴上代码。

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register
typedef long long ll;

const int MAXN=1010;	//最长 1000位
const int BYTE=8;		//压8位,也就是说一个int代表8位数
const int MB=MAXN/BYTE+1;	//新的长度

int pw[BYTE+10];	//pw[i]表示10^i
char str[MAXN+10];


struct Bignum{		//高精度类
	int a[MB+10];
	Bignum(){
		memset(a,0,sizeof(a));
	}
	void read(){	//读入、赋值、压位
		scanf("%s",str+1);
		int len=strlen(str+1);
		for(reg int i=1;i<=len;++i)
			a[i]=str[len-i+1]-'0';
		for(reg int i=1;i<=len/BYTE+1;++i){
			int cnt=0;
			for(reg int j=i*BYTE;j>=(i-1)*BYTE+1;--j)
				cnt=cnt*10+a[j];
			a[i]=cnt;
		}
		//注意清零
		for(reg int i=len/BYTE+2;i<=len;++i) a[i]=0;
	}
	void print(){	//打印一个数
		bool tf=0;
		for(reg int i=MB+1;i>=1;--i){
			if(a[i]&&!tf){
				tf=1;
				printf("%d",a[i]);
				continue;
			}
			if(tf) printf("%08d",a[i]);
		}
		if(!tf) putchar('0');
	}
	//重定义Bignum类的运算,高精+高精
	friend Bignum operator+(const Bignum a,const Bignum b){
		Bignum c;
		for(reg int i=1;i<=MB+1;++i)
			c.a[i]=a.a[i]+b.a[i];
		for(reg int i=1;i<=MB+1;++i)
			if(c.a[i]>=pw[BYTE]){
				c.a[i+1]+=c.a[i]/pw[BYTE];
				c.a[i]%=pw[BYTE];
			}
		return c;
	}
	//高精减
	friend Bignum operator-(const Bignum a,const Bignum b){
		Bignum c;
		for(reg int i=1;i<=MB+1;++i)
			c.a[i]=a.a[i]-b.a[i];
		for(reg int i=1;i<=MB+1;++i)
			if(c.a[i]<0){
				c.a[i]+=10;
				--c.a[i+1];
			}
		return c;
	}
	//高精乘低精
	friend Bignum operator*(const Bignum a,const int b){
		Bignum c;
		ll d[MB+10];
		for(reg int i=1;i<=MB+1;++i)
			d[i]=(ll)a.a[i]*b;
		for(reg int i=1;i<=MB+1;++i){
			if(d[i]>=pw[BYTE]){
				d[i+1]+=d[i]/pw[BYTE];
				d[i]%=pw[BYTE];
			}
			c.a[i]=(int)d[i];
		}
		return c;
	}
	//注意此处的>等价于>=
	friend bool operator>(const Bignum a,const Bignum b){
		for(reg int i=MB+1;i>=1;--i)
			if(a.a[i]!=b.a[i])
				return a.a[i]>b.a[i];
		return 1;
	}
}ft[MB+10],k,one;
//ft[i]表示i的阶乘
struct Pair{	//两个大数
	Bignum a,b;
};
int n;
bool tf[MAXN+10];	//tf[i]表示第i个数是否可以被取用
int ans[MAXN+10];	//ans是答案数组

//朴素高精除,返回的a是商,b是余数
Pair operator/(const Bignum a,const Bignum b){
	Pair c;Bignum d;
	for(reg int i=MB+1;i>=1;--i){
		d=d*10;d.a[1]=a.a[i];
		c.a=c.a*10;
		while(d>b){
			d=d-b;
			++c.a.a[1];
		}
	}
	c.b=d;
	return c;
}
void reset(){	//初始化
	pw[0]=1;
	for(reg int i=1;i<=BYTE;++i)
		pw[i]=pw[i-1]*10;
	ft[1].a[1]=1;
	for(reg int i=2;i<=n;++i)
		ft[i]=ft[i-1]*i;
	memset(tf,1,sizeof(tf));
	one.a[1]=1;
	k=k-one;
}
int cg(const Bignum a){	//将一个Bignum转换成int
	int cnt=0;
	for(reg int i=MB+1;i>=1;--i)
		cnt=cnt*pw[BYTE]+a.a[i];
	return cnt;
}
int find(int x){	//找到能使用的第x小的数
	int cnt=0;
	for(reg int i=1;i<=n;++i)
		if(tf[i]){
			++cnt;
			if(cnt==x){
				tf[i]=0;	//取用它
				return i;
			}
		}
	return -1;
}
void work(){	//DeCantor Expansion
	Pair p;
	int cnt;
	//此处参考算法解释的文章
	for(reg int i=1;i<n;++i){
		p=k/ft[n-i];k=p.b;
		cnt=cg(p.a);
		ans[i]=find(cnt+1);
	}
	ans[n]=find(1);	//最后只剩一个数了,取用它
}
int main(){
	scanf("%d",&n);k.read();
	reset();
	work();
	for(reg int i=1;i<=n;++i)
		printf("%d ",ans[i]);
}

最后一个点的 100 000100\ 000,看上去很吓人,但是由于 k1020 000k\leq10^{20\ 000},所以前 96 00096\ 000 位左右都是原来的顺序(本质上还是 n2n^2 的)。这个点的特判我不管了你自己写。

优化方法

  1. 使用树状数组维护一个数是否被取用。时间复杂度 O(n log n)O(n\ \log\ n)
  2. 快速多项式除法,或者二分答案。时间复杂度 O(n log n)O(n\ \log\ n)
  3. 使用压位存储。若压 BYTEBYTE 位,则时间复杂度 O(O()BYTE2)O(\frac{O(原来)}{BYTE^2})
  4. 使用任意模数快速阶乘。时间复杂度 O(n log n)O(n\ \log\ n)(未实践)。

期望时间复杂度 O(n log n)O(n\ \log\ n)。若哪位巨佬实现了,请评论并收下我的膝盖orz。

posted @ 2019-04-27 20:28  TeacherDai  阅读(227)  评论(0)    收藏  举报