1 条题解

  • 0
    @ 2023-5-26 11:35:44

    题目大意

    • 给定一个长度为 nn 的序列 aa,求将序列中所有的 1-1 替换成一个正整数使得 $\{a_1,\ a_2,\ \cdots,\ a_n\}=\{1,\ 2,\ \cdots,\ n\}$ 且  1in, aii\forall\ 1\leq i\leq n,\ a_i\not=i 的方案数目模 109+710^9+7

    • 2n2×1032\leq n\leq 2\times 10^3

    分析

    显然当  ai=i\exists\ a_i=i 时,答案为 00

    ai=1a_i=-1 的位置 ii 分为两类:

    • 第一类, aji\forall\ a_j\not=i,即 ii 这个数的位置还未确定,则该位置只能放除了 ii 以外的数;

    • 第二类, aj=i\exists\ a_j=i,即 ii 这个数的位置已经确定,则该位置放的数不受限制。

    设第一类的位置有 tot1tot_1 个,第二类的位置有 tot2tot_2 个。问题转化为有 tot1tot_1 个位置只能放除了 ii 以外的数,有 tot2tot_2 个位置放的数不受限制,求方法数。

    解法 1

    容斥原理。

    考虑满足 ai=ia_i=i 的位置 ii 的个数,下面研究至少有 kk 个位置满足 ai=ia_i=i 的方法数。

    从只能放除了本身以外的数的 tot1tot_1 个位置中,选出 kk 个放本身值,有 Ctot1kC_{tot_1}^k 种情形。剩余 tot1+tot2itot_1+tot_2-i 个位置任意放,有 (tot1+tot2k)!\left(tot_1+tot_2-k\right)! 种情形。由乘法原理,方法数为

    (tot1k)(tot1+tot2k)!.\binom{tot_1}k\cdot\left(tot_1+tot_2-k\right)!.

    由容斥原理,最终方法数为

    $$\sum_{i=0}^{tot_1}\binom{tot_1}i\cdot\left(tot_1+tot_2-i\right)!\cdot\left(-1\right)^i. $$

    预处理组合数时间复杂度 Θ(n2)\Theta(n^2),预处理阶乘时间复杂度 Θ(n)\Theta(n),计算时间复杂度 Θ(n)\Theta(n),最终时间复杂度 Θ(n2)\Theta(n^2)

    AC 代码

    #include<bits/stdc++.h>
    using namespace std;
    const long long mod=1e9+7;
    int n,a[2001],tot1,tot2;
    //tot1: 不能放自身的位置个数 
    //tot2: 可以随便放的位置个数 
    long long answer;
    bool p[2001];
    
    long long fac[2001],C[2001][2001];
    void initfac(int N){
    	fac[0]=1;
    	for(int i=1;i<=N;i++)
    		fac[i]=fac[i-1]*i%mod;
    }
    void initC(int N){
    	for(int i=0;i<=N;i++)
    		C[i][0]=1;
    	for(int i=1;i<=N;i++)
    		for(int j=1;j<=i;j++)
    			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",a+i);
    		if(a[i]==i){
    			printf("0\n");
    			return 0;
    		}
    		if(a[i]!=-1)p[a[i]]=true;
    		else tot1++;
    	}
    	initfac(tot1);
    	for(int i=1;i<=n;i++)
    		if(a[i]==-1&&p[i])tot1--,tot2++;
    	initC(tot1);
    	for(int i=0;i<=tot1;i++){
    		int t=(i&1)?-1:1;
    		answer+=fac[tot1+tot2-i]*C[tot1][i]%mod*t;
    		answer=(answer+mod)%mod;
    	}
    	printf("%lld\n",answer);
        return 0;
    }
    

    解法 2

    二维动态规划。

    设状态 dpi, jdp_{i,\ j} 表示有 ii 个位置只能放除了本身以外的数,有 jj 个位置放的数不受限制时的方法数。则答案为 dptot1, tot2dp_{tot_1,\ tot_2}

    显然有边界 dp0, j=j!dp_{0,\ j}=j!,下面讨论边界 dpi, 0dp_{i,\ 0}

    位置 ii 只能放 1i11\sim i-1 的数,有 i1i-1 种可能。不妨设位置 ii 放的是 xx

    • 位置 xx 放的是 ii。此时有 dpi2, 0dp_{i-2,\ 0} 种情况;
    • 位置 xx 放的不是 ii。此时等价于 dpi1, 0dp_{i-1,\ 0} 的情况。

    综上所述,$dp_{i,\ 0}=(i-1)\left(dp_{i-1,\ 0}+dp_{i-2,\ 0}\right)$。

    讨论 dpi, jdp_{i,\ j}i, j1i,\ j\geq 1)。任取一放的数不受限制的位置。

    • 若放的是这 ii 个数中的数,有 dpi1, jdp_{i-1,\ j} 种情况,共有 idpi1, ji\cdot dp_{i-1,\ j} 种情况;

    • 若放的是这 jj 个数中的数,有 dpi, j1dp_{i,\ j-1} 种情况,共有 jdpi, j1j\cdot dp_{i,\ j-1} 种情况;

    综上所述,dpi, j=idpi1, j+jdpi, j1dp_{i,\ j}=i\cdot dp_{i-1,\ j}+j\cdot dp_{i,\ j-1}

    时间复杂度 Θ(n2)\Theta(n^2)

    AC 代码

    #include<bits/stdc++.h>
    using namespace std;
    const long long mod=1e9+7;
    int n,a[2001],tot1,tot2;
    //tot1: 不能放自身的位置个数 
    //tot2: 可以随便放的位置个数 
    long long dp[2001][2001];
    //dp[i][j]: 有 i 个位置不能放自身,j 个位置可以随便放 
    bool p[2001];
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",a+i);
    		if(a[i]==i){
    			printf("0\n");
    			return 0;
    		}
    		if(a[i]!=-1)p[a[i]]=true;
    		else tot1++;
    	}
    	for(int i=1;i<=n;i++)
    		if(a[i]==-1&&p[i])tot1--,tot2++;
    	dp[0][0]=1;
    	for(int i=2;i<=tot1;i++)
    		dp[i][0]=(dp[i-1][0]+dp[i-2][0])%mod*(i-1)%mod;
    	for(int j=1;j<=tot2;j++)
    		dp[0][j]=dp[0][j-1]*j%mod;
    	for(int i=1;i<=tot1;i++)
    		for(int j=1;j<=tot2;j++)
    			dp[i][j]=(i*dp[i-1][j]%mod+j*dp[i][j-1]%mod)%mod;
    	printf("%d\n",dp[tot1][tot2]);
        return 0;
    }
    

    解法 3

    动态规划。

    设状态 dpidp_i 表示有 ii 个位置只能放除了本身以外的数,有 tot2tot_2 个位置放的数不受限制时的方法数。则答案为 dptot1dp_{tot_1}

    显然有边界 dp0=tot2!dp_0=tot_2!,下面讨论 dpidp_ii1i\geq 1)。

    • ii 这个数放的是不受限制的 tot2tot_2 个位置中的一个,有 tot2dpi1tot_2dp_{i-1} 种情况;

    • 其余情况类似「解法 2」中错位排列的求法,有 (i1)(dpi1+dpi2)(i-1)(dp_{i-1}+dp_{i-2}) 中情况。

    综上所述,dpi=tot2dpi1+(i1)(dpi1+dpi2)dp_i=tot_2dp_{i-1}+(i-1)(dp_{i-1}+dp_{i-2})

    注意 i=1i=1 时,dpi2dp_{i-2} 会发生数组越界,需要特殊处理。

    时间复杂度 Θ(n)\Theta(n)

    AC 代码

    #include<bits/stdc++.h>
    using namespace std;
    const long long mod=1e9+7;
    int n,a[2001],tot1,tot2;
    //tot1: 不能放自身的位置个数 
    //tot2: 可以随便放的位置个数 
    long long answer;
    bool p[2001];
    long long dp[2001]={1};
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",a+i);
    		if(a[i]==i){
    			printf("0\n");
    			return 0;
    		}
    		if(a[i]!=-1)p[a[i]]=true;
    		else tot1++;
    	}
    	for(int i=1;i<=n;i++)
    		if(a[i]==-1&&p[i])tot1--,tot2++;
    	for(int i=1;i<=tot2;i++)dp[0]=dp[0]*i%mod; 
    	dp[1]=tot2*dp[0]%mod;
    	for(int i=2;i<=tot1;i++){
    		dp[i]=(dp[i-1]+dp[i-2])%mod*(i-1)%mod;
    		dp[i]=(dp[i]+tot2*dp[i-1]%mod)%mod;
    	}
    	printf("%lld\n",dp[tot1]);
        return 0;
    }
    
    • 1

    信息

    ID
    5581
    时间
    1000ms
    内存
    256MiB
    难度
    7
    标签
    递交数
    2
    已通过
    2
    上传者