康拓展开--P5367 【模板】康托展开

举个栗子大家应该就能懂了:

我现在生成了1~5的全排列,求数列5  2  3  1  4是第几个

5:有四个数比他小,他后面还有4个数,所以他的贡献应该是4*4!

2:有一个数比他小,他后面还有3个数,所以他的贡献应该是1*3!

3:有两个数比他小,他后面还有两个数,但是其中2在前面出现过了,所以他的贡献应该是1*2!

1:有零个数比他小,他后面还有一个数,所以他的贡献应该是0*1!

4:有三个数比他小,他后面还有0个数,但是1 2 3都在前面出现过,所以他的贡献应该是0*0!

所以数列5  2  3  1  4的前面有4*4!+1*3!+1*2!+0*1!+0*0!个数列

它本身就是第4*4!+1*3!+1*2!+0*1!+0*0!+1个数列

如何知道比他小的数字的个数,和树状数组维护逆序对的原理一样,初始时都为1,如果出现过,就-1,求前缀和

而阶乘我们可以用秦九韶算法优化,比如上面的式子就=((((4*4+1)*3)+1)*2+0)*1+0*1+1

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 using namespace std;
 5 #define int long long
 6 const int maxn=1e6+10,mod=998244353;
 7 int n;
 8 int a[maxn],tmp[maxn],sum[maxn];
 9 void fix(int x,int k){
10   for (int i = x;i <= n;i+=i&(-i)) sum[i]+=k;
11 }
12 int query(int x){
13   int res=0;
14   for (int i = x;i >= 1;i-=i&(-i)) res+=sum[i];
15   return res;
16 }
17 signed main(){
18   scanf ("%lld",&n);
19   for (int i = 1;i <= n;i++) fix(i,1);
20   for (int i = 1;i <= n;i++) scanf ("%lld",&a[i]);
21   for (int i = 1;i <= n;i++){
22     tmp[i]=query(a[i]-1);
23     fix(a[i],-1);
24   }
25   int ans=tmp[1]*(n-1);
26   for (int i = 2;i <= n-1;i++){
27     ans=ans+tmp[i]; ans=ans%mod;
28     ans=ans*(n-i); ans=ans%mod;
29   }
30   ans=ans+1;ans+=ans*tmp[n]*1;ans=ans%mod;
31   printf("%lld\n",ans);
32   return 0;
33 }

 

posted @ 2020-10-01 11:04  小又又  阅读(108)  评论(1编辑  收藏  举报