2018年9月22日提高组模拟赛 T2 今天你AK了吗

版权声明:转载无所谓的。。。 https://blog.csdn.net/xuxiayang/article/details/82812120

大意

n n 的全排列的第 k k


思路

裸的逆康拓展可以直接拿60,套上高精度可拿70(优化可到80,甚至90)

但是,我们也可以通过一些公式来优化

k n ! = k n 1 n \frac{k}{n!}=\frac{\frac{k}{n-1}}{n}

具体证明我也不懂,总之就是把逆康拓展先打出来,然后按公式改即可

具体证明:
就是这张
还有下面这个玩意儿。。。

x   m o d   y z   d i v   z = x   d i v   z   m o d   y x\ mod\ yz\ div\ z=x\ div\ z\ mod\ y

证明:

a = a   d i v   z , b = x a y z 设a=a\ div\ z,b=x-ayz x = a y z + b x=ayz+b

x   m o d   y z   d i v z = b   d i v   z x\ mod\ yz\ div z=b\ div\ z

x   d i v   z   m o d y = ( a y z + b )   d i v   z   m o d   y = ( a y + b   d i v   z )   m o d y = b   d i v   z   m o d   y x\ div\ z\ mod y=(ayz+b)\ div\ z\ mod\ y=(ay+b\ div\ z)\ mod y=b\ div\ z\ mod\ y

b = x   m o d   y z \because b=x\ mod\ yz

b < y z \therefore b<yz

b   d i v   z < y \therefore b\ div\ z<y

x   m o d   y z   d i v   z = x   d i v   z   m o d   y \therefore x\ mod\ yz\ div\ z=x\ div\ z\ mod\ y

发现每次的除数都是之前的约数。
所以对于第i次除法的商,可以直接用 k   m o d   ( n i + 1 ) !   d i v   ( n i ) ! k\ mod\ (n - i +1)!\ div\ (n - i)! 得到。
根据第二个结论, k   m o d   ( n i + 1 ) !   d i v   ( n i ) ! = k   d i v   ( n i ) !   m o d   ( n i + 1 ) k\ mod\ (n - i +1)!\ div\ (n - i)!=k\ div\ (n - i)!\ mod\ (n - i + 1)
观察这个式子,再结合推论1,我们发现可以从大到小枚举 i i ,每次对 k k 除以i,得到的余数就是原第 ( n i + 1 ) (n-i+1) 次除法的商。
因此求商的复杂度降为 O ( w 2 ) O(w^2) w w k k 在十进制下有多少位。
由于只需要单精度除法,可以用long long压8位来做,并且随着被除数的不断减小,每次除法的复杂度降低。
所以复杂度大概是 O ( w 2 / 40 ) O(w^2/40)
这一部分的复杂度和 n n 无关

最后一个问题,怎么快速找当前没有被选的数中的第a大的数?
直接暴力是 n 2 n^2 的。
套用数据结构(zkw)线段树,树状数组,splay或者分块思想都可以解决这个问
题,但是这显然超纲了

观察数据发现, 6100 ! > 1 0 20000 6100!>10^{20000} ,什么意思?

也就是最后一组数据的前 20000 6100 = 13900 20000-6100=13900 都为 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 13897 , 13898 , 13898 , 13899 , 13900 1,2,3,4,5,6,7,8……13897,13898,13898,13899,13900 这样我们就只需要在后面 6100 6100 个数中查找没有被选中的第 a a 大的数

这样的复杂度就是 O ( 610 0 2 ) O(6100^2) 的,可以卡过本题。。。


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ymw 100000000//高精度压8位
using namespace std;
int l,n,len,t,k;char s[100001];
long long a[4001],kth[100001];
bool bz[100001];
signed main()
{
	scanf("%d %s",&n,s);
	len=strlen(s);
	l=4000;t=1;
	for(register int i=len-1;i>=0;i--)
	{
		a[l]+=(s[i]-48)*t;
		t=(t<<3)+(t<<1);
		if(t==ymw) t=1,l--;//每八位换下一个
	}
	int j=4000;
	for(;!a[j];a[j]=ymw-1,j--);//把所有的0都当成为99999999
	--a[j];//因为逆康拓展是求前面有多少个比它大的,而我们是要求第k个,所以要减一
	for(register int i=1;i<=n;i++)
	{
		for(register int j=l;j<4000;j++)
		{
			long long x=a[j]/i;
			a[j+1]+=ymw*(a[j]-x*i);a[j]=x;//高精度除法
		}
		kth[n-i+1]=a[4000]%i+1;a[4000]/=i;//最后一位特判
		while(!a[l]) l++;//没了就往后推,节省循环
	}
	k=max(0,n-6100);//前面的数直接输出
	for(register int i=1;i<=k;i++) printf("%d ",i);//输出
	for(register int i=k+1;i<=n;i++)
	{
		int j=k+1,cnt=0;
		for(;cnt<kth[i];j++) if(!bz[j])++cnt;//找到第cnt个
		bz[--j]=1;printf("%d ",j);//前面cnt个都找到了,输出
	}
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/82812120
今日推荐