康托(Cantor)展开

直接进入正题。

康托展开

Description

现在有"ABCDEFGHIJ”10个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的?

Input

第一行有一个整数n(0

随后有n行,每行是一个排列;

Output

输出一个整数m,占一行,m表示排列是第几位;

Sample Input
3
ABCDEFGHIJ
HGEBFACDJI
GDEDHJBXIA
Sample Output
1
2803322
1911924
 
其实思路可能不用说也能想出来
设总长度为len,枚举每一个字母,找它后面比它小的字母个数,记为k。那么每一位的答案就是k*(len-i)!。
下面给出草率证明:
先举个栗子,比如说一个字符串BACED。(样例太长懒得解释)
第一位B找到了A一个比它小的字母,说明这个字符串排在所有以A开头的字符串之后(显然共有4!个),所以B对最终答案的贡献是4!。
第二位A没找到比它小的字母,说明这个字符是所有以B开头的字符串中第二位最小的,所以B对最终答案没有贡献。
第三位C没找到比它小的字母,说明这个字符是所有以BA开头的字符串中第二位最小的,所以C对最终答案没有贡献。
第四位E找到了E一个比它小的字母,说明这个字符串排在所有以BACD开头的字符串之后(显然共有1!个),所以E对最终答案的贡献是1!。
第五位D没找到比它小的字母,说明这个字符是所有以BACE开头的字符串中第二位最小的,所以D对最终答案没有贡献。
又因为第一个字符串排名为1,所以所有字符串排名都应加1。
所以答案是1+4!+1!=26;
 
放代码,算是很短的。
 1 #include
 2 using namespace std;
 3 
 4 int n,f[19];
 5 int main(){
 6     scanf("%d",&n);
 7     f[0]=1;//注意 
 8     for(int i=1;i<=10;i++) f[i]=f[i-1]*i;//预处理阶乘 
 9     while(n--)//数据组数 
10     {
11         int ans=1;//每个字符串排名都加1,上面有提到 
12         string s;
13         cin>>s;
14         int len=s.length();//取字符串长度 
15         for(int i=0;is[j]) k++;//记录在当前字符之后有几个比当前字符小的 
20             ans+=k*f[len-i-1];//注意i从0开始,所以是len-i-1 
21         }
22         printf("%d\n",ans);
23     }
24     return 0;
25 }
View Code

 但是时间复杂度达到了len2(当然这里len只有11,除了穷举都能过)

 

但如果是这道题呢?(参考洛谷P5637)

P5367 【模板】康托展开

题目描述

1∼N的一个给定全排列在所有1∼N全排列中的排名。结果对998244353取模。

输入格式

第一行一个正整数N

第二行N个正整数,表示1∼N的一种全排列。

输出格式

一行一个非负整数,表示答案对9982443539取模的值。

输入输出样例

输入 #1 
3
2 1 3
输出 #1 
3
输入 #2 
4
1 2 4 3
输出 #2 
2

说明/提示

对于10%数据,1N10。

对于50%数据,1N5000。

对于100%数据,1≤N≤1000000

 

如果用传统的康托展开做,时间复杂度O(n2),只能拿50分。

我们考虑用数据结构维护每个数后面的它小的数的个数,自然而然想到了权值线段树其实是我不会树状数组)。

线段树的叶子节点维护的是该叶子结点所对应的编号出现的次数(我也说不清楚,具体看代码)。首先插入所有数,随后第i次找1到a[i]-1区间内的和,做完之后删除这个数,防止后面的数重复记录产生错误答案。时间复杂度O(logn)。

上代码:

 1 #include//随时记得取模,尽可能降低WA的概率 
 2 #define P 998244353
 3 #define N 1000009
 4 using namespace std;
 5 
 6 int n,ans=1,a[N],sum[N<<2]; 
 7 long long f[N];//这道题内存限制只有31.25MB,第一次全开了long long发现MLE,第二次全开了int爆WA,最后部分开了long long才压内存过
 8 void Update(int rt,int l,int r,int x,int c,int fg)//fg是flag,标记 
 9 {
10     if(l==r && l==x)
11     {
12         if(fg==1) sum[rt]+=c;//如果是插入,sum就加c 
13         else sum[rt]-=c;//如果是删除,sum就减c 
14         return;
15     }
16     int mid=(l+r)>>1;
17     if(x<=mid) Update(rt<<1,l,mid,x,c,fg);
18     else Update(rt<<1|1,mid+1,r,x,c,fg);
19     sum[rt]=sum[rt<<1]+sum[rt<<1|1];
20 }
21 
22 int Query(int rt,int l,int r,int x,int y)
23 {
24     if(l==x && r==y) return sum[rt];
25     int mid=(l+r)>>1;
26     if(y<=mid) return Query(rt<<1,l,mid,x,y);
27     else if(x>mid) return Query(rt<<1|1,mid+1,r,x,y);
28     else return Query(rt<<1,l,mid,x,mid)+Query(rt<<1|1,mid+1,r,mid+1,y);
29 }
30 int main(){
31     memset(sum,0,sizeof(sum));
32     scanf("%d",&n);
33     f[0]=1;//注意 
34     for(int i=1;i<=1000009;i++) f[i]=f[i-1]%P*i%P;//计算阶乘 
35     for(int i=1;i<=n;i++)
36     {
37         scanf("%d",&a[i]);
38         Update(1,1,n,a[i],1,1);//将每一个数插入到权值线段树中 
39     }
40     for(int i=1;i<=n;i++)
41     {
42         if(a[i]==1)//如果a[i]==1,那么该数一定对答案没有贡献,直接删除 
43         {
44             Update(1,1,n,a[i],1,0);
45             continue;
46         }
47         ans=(ans+Query(1,1,n,1,a[i]-1)%P*f[n-i]%P)%P;//更新答案 
48         Update(1,1,n,a[i],1,0);//删除这个数 
49     }
50     printf("%d\n",(ans+P)%P);
51     return 0;
52 }
View Code

 

 

你可能感兴趣的:(康托(Cantor)展开)