E. Partial Sorting 数学

https://codeforces.ml/contest/1768/problem/E

更新,感谢本人高中同学网名大肥花狗,指正了我文章的错误,已经做出修改。

题意:给你一个n,问你,对于长度为3n的所有排列,要多少次操作才能让全部的排列变成升序。在一次操作中,你可以对前2n个排序,也可以对后面2n个排序。

解法:我们可以将排列分为四种。

第一种:123……3n这个已经升序的了,操作是0次,我们就别管他了。

第二种:需要操作1次的,就是在前2n无序、后n已经排好,或者是后2n无序,前n已经排好的。

第三种:需要操作2次的,前2n无序,后n也无序,但是前n都在前2n里面,所以两次就完事了。反之亦然。

第四种:剩下的组合。

第一次想的时候第二种把排好的想成了有序的就行了,不知道脑子在想什么,很烦。

以k1表示第一种,类推那么ans=3(3n!)-3*k1-2*k2-1*k3-0*k4;

我们在下面求的时候,发现经常k2包括k1等,不用管了,一起减去才是对的。

在开始想怎么求出来这些的数量。

第一种的数量为1,操作数为0。

第二种的数量为2*((2n)!)-n!。n!是指n到2n这一块无序的,都能符合。

第三种的数量嘛,很难想。首先前n个只能放进2n个位置里面,这是一个从2n中选n个的组合,然后这后n个自己排列,前2n个自己排列,因为反过来也一样,所以乘2。但是也重了一部分,就是如果前n个都在前2n里面,后n个也都在n到3n里面。所以要减去(从n取i的组合)*(从n中取n-i的组合)*(从n+i中取n的组合)*(n!)*(n!)*(n!)。(顺便一提,这个算式有对称之美,如果算式拟人化,这位一定是美女。)

到此结束,下面是ac代码

#include<bits/stdc++.h>

#define int long long

using namespace std;

const int maxn = 3e6 + 5;

int fect[maxn], infect[maxn];

int m;

int binpow(int a,int b,int m)

{

  a %= m;

  int res = 1;

  while (b > 0) {

    if (b & 1) res = res * a % m;

    a = a * a % m;

    b >>= 1;

  }

  return res;

}

int C(int a,int b)

{

return fect[a]*infect[b]%m*infect[a-b]%m;

}

void solve()

{

int n;

cin>>n>>m;

fect[0]=1;

infect[0]=1;

for(int i=1;i<=3*n;i++)

{

fect[i]=(fect[i-1]*i)%m;

}

  infect[3*n-1]=binpow(fect[3*n-1],m-2,m);

    for(int i=3*n-2;i>=1;i--)

    infect[i]=infect[i+1]*(i+1)%m;

    int cnt0=1;

//第二种

int cnt1=2*fect[2*n]-fect[n];

//第三种

int cnt2=C(2*n,n)*fect[n]%m*fect[2*n]%m*2%m;

int temp=fect[n]*fect[n]%m*fect[n]%m;

for(int i=0;i<=n;i++)

{

cnt2-=C(n,i)%m*C(n,n-i)%m*C(n+i,n)%m*temp%m;

}

cnt2%=m;

int cnt3=fect[3*n];

int ans=(((cnt3*3-cnt2-cnt1-cnt0)%m)+m)%m;

cout<<ans<<"\n";

}

signed main()

{

std::ios::sync_with_stdio(false);

cin.tie(0);

cout.tie(0);

// int t;

// cin>>t;

// while(t--)

solve();

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值