Sort(置换,排序,贝尔数)

该博客探讨了一种排列问题,其中存在不确定位置。通过分析置换为环的概念,得出结论:当环大小不超过2时只需一步操作,否则两步。不同长度的环独立处理,方案数为环长度的乘积。对于k个不确定位置,通过枚举路径划分,以O(n log n + bell(k) × k)的时间复杂度求解最小操作次数和方案数。
摘要由CSDN通过智能技术生成

题意:
给出一个长度为 n ( ≤ 1 e 5 ) n(\leq 1e5) n(1e5)的排列,其中有 K ( ≤ 12 ) K(\leq 12) K(12)个位置不确定,在填好这些不确定的位置后得到的所有排列,若干次选很多对位置将每对位置上的两个数交换位置,求所有排列排成升序所需的最少操作次数之和,在最少操作次数的情况下的操作方案数之和。

结论题。
1.排列可以看做置换,置换可以看做若干个环,环的大小均 ≤ 2 \leq 2 2时只需要一步,方案只有一种,否则也只需要两步。
2.对于一个长度为 n n n的环,不难发现,在第一步后的第一位填 [ 1 , n ] [1, n] [1,n] 中任意一个值之后,第一步后的剩余位置均可直接推出,且不会产生矛盾。这样第一步后的第一位的值就对应一种方案,即
共有 n n n 种方案。
3.对于两个长度为 n n n的环,考虑跨环的交换会发生什么,在第一步后第一位填另一个环内的任意一个值之后,其余位置又可以直接推出且不出现矛盾。这样就又出现了 n 种方案。
4.根据之前的分析可以知道,一个环要么单独排序,方案数为 n;要么和另一个长度相同的环一起排序,方案数也为 n。
5.长度不同的环之间如果跨环交换,则无法在两步之内排好序。于是每一种长度的环是独立的,将每一种长度的方案数相乘即可。
6.k > 0 时,原排列可以看成若干个环加上 k 条路径,补全排列实际上是将这 k条路径组合成环。
直接枚举排列复杂度过高,由于方案数只与环的大小有关,而与路径组合成环的顺序无关,考虑枚举 k 条路径的每一种划分。预处理出 dp(i, j), i ∈ [1, n], j ∈ [0, n/i],并计算 dp(i, j) 的逆元。开个数组记
录原排列中每种长度的环的个数,并计算仅考虑这些环时的方案数。枚举完一个划分后只会最多增加 k 个环,可以在 O(k) 内算出新的方案数。
总复杂度为 O(n log n + bell(k) × k)。

这波,这波是时间管理。
A C   C o d e \rm AC\ Code AC Code

#include<bits/stdc++.h>
#define maxn 100005
#define mod 1000000007
using namespace std;

int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }

int n,K;
int *f[maxn],*fr[maxn],a[maxn],b[maxn];
bool usd[maxn];
int ar[maxn],tot,c[maxn];
int sg,sf,sm=1,ct=0,csm[1<<12],lg[1<<12],bit[1<<12],fac[13]={1};

void dfs(int sta,int coe){
	if(!sta){
		if(ct == 0){
			if(c[2])
				sf += coe , sg+= coe;
			else
				sg += coe;
		}
		else sf += 2 * coe , sg = (sg + 1ll * coe * sm) % mod;
		return;
	}
	int t = sta & -sta;
	for(int s=sta;s;s=(s-1)&sta) if(s & t){
		int p = csm[s];
		if(p > 2) ct ++;
		sm = 1ll * sm * fr[p][c[p]] % mod * f[p][c[p]+1] % mod;
		c[p]++;
		dfs(sta-s,coe * 1ll * fac[bit[s]-1] % mod);
		sm = 1ll * sm * fr[p][c[p]] % mod * f[p][c[p]-1] % mod;
		c[p]--;
		if(csm[s] > 2) ct --;
	}
}

int main(){
	
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;i++){
		f[i] = new int[n / i + 2];
		fr[i]= new int[n / i + 2];
		f[i][0] = 1 , f[i][1] = i;
		fr[i][0] = 1 , fr[i][1] = Pow(i,mod-2);
		for(int j=2;j<=n/i;j++)
			f[i][j] = (f[i][j-1] * 1ll * i+ 1ll * i * f[i][j-2] % mod * (j-1)) % mod,
			fr[i][j] = Pow(f[i][j],mod-2);
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
	for(int i=1;i<=n;i++) if(!a[i]){
		int cnt = 1;
		usd[i]=1;
		for(int j=b[i];j;j=b[j]) usd[j]=1,cnt++;
		ar[tot++] = cnt;
	}
	for(int i=0;i<K;i++) lg[1<<i] = i;
	for(int i=1;i<=K;i++) fac[i] = 1ll * fac[i-1] * i % mod;
	for(int i=1;i<(1<<K);i++) bit[i] = bit[i>>1] + (i&1);
	for(int i=1;i<=n;i++) if(!usd[i]){
		int cnt = 0;
		for(int j=i;!usd[j];j=a[j])
			usd[j] = 1 , cnt++;
		c[cnt]++;
	}
	for(int i=1;i<=n;i++) if(c[i]){
		sm = 1ll * sm * f[i][c[i]] % mod;
		if(i > 2) ct += c[i];
	}
	for(int i=1;i<(1<<K);i++)
		csm[i] = csm[i - (i&-i)] + ar[lg[i&-i]];
	dfs((1<<K)-1,1);
	printf("%d\n%d\n",sf,sg);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值