nssl1156-今天你AK了吗?【康托展开,高精度,二分答案,树状数组】

正题


题目大意

求n个数的全排列的第k个。


解题思路

首先康拓逆展开
∑ i i < = n x i ( n − i ) ! \sum^{i<=n}_i x_i(n-i)! ii<=nxi(ni)!
求每个时候第 x x x大的数
然后因为 n ( n − 1 ) ! = n ! n(n-1)!=n! n(n1)!=n!
so我们可以直接用余数
nssl1156-今天你AK了吗?【康托展开,高精度,二分答案,树状数组】_第1张图片
这是 n = 3 n=3 n=3时是序列,我们可以发现我们需要求第 x i x_i xi个。这时候我们可以用二分加树状数组来 l o g 2 log^2 log2的求答案。
之后我们又会发现
∑ i i < = n x i ( n − i ) ! \sum^{i<=n}_i x_i(n-i)! ii<=nxi(ni)!
这个直接计算时间复杂度是 n 2   l o g 2 n   w n^2\ log^2n\ w n2 log2n w(w是高进度位数)。
这时我们引入:
x % y z / z = x / z % y x\%yz/z=x/z\%y x%yz/z=x/z%y
证明:
nssl1156-今天你AK了吗?【康托展开,高精度,二分答案,树状数组】_第2张图片
这时候我们可以枚举 1 ∼ n 1\sim n 1n,然后每次 k   m o d   i k\ mod\ i k mod i,这时候的余数就是上面式子 n − i + 1 n-i+1 ni+1次的余数。


code

#include
#include
#define lobit(x) x&-x
#define N 100010
#define ll long long
using namespace std;
ll a[N*2],n,t[N],l,mo[N];
char k[N*2];
void change(ll x,ll num)//改变
{
	while(x<=n)
	{
		t[x]+=num;
		x+=lobit(x);
	}
}
ll ask(ll x)//询问
{
	ll sum=0;
	while(x)
	{
		sum+=t[x];
		x-=lobit(x);
	}
	return sum;
}
void read()//输入——高精度
{
	scanf("%s",k);
	l=strlen(k)-1;
	ll L=0;
	for(ll i=l;i>=0;i--) 
	  L+=((l-i)%13)==0,mo[i]=L-1;//压行之后的位置
	for(ll i=0;i<=l;i++) 
	  a[mo[i]]=a[mo[i]]*10+k[i]-48;//求值
	a[0]--;
	for(ll i=0;i<=l;i++) 
	  if(a[i]<0) a[i+1]--,a[i]+=(1e13);//要先-1
	l=L-1;
}
ll div(ll x)//高精除
{	
	ll g=0;
	for (ll i=l;i>=0;i--)
	{
		ll s;
		s=g*(1e13)+a[i];
		a[i]=s/x;
		g=s%x;
	}
	while(!a[l]) l--;
	return g;
}
int main()
{
	scanf("%lld",&n);
	read();
	for(ll i=1;i<=n;i++)
	  mo[n-i+1]=div(i);//计算余数
	for(ll i=1;i<=n;i++) {
		ll ans=0;
		for(ll l=1,r=n;l<=r;) {
			ll m=(l+r)/2;
			ll s=0,x=m;
			s=x-ask(x);
			if(s<=mo[i]) l=m+1;
			else ans=m,r=m-1;
		}//二分位置
		ll x=ans;
		change(x,1);//去掉这个数
		printf("%lld ",ans);
	}
	return 0;
}

你可能感兴趣的:(高精度)