【JZOJ3059】【NOIP2012模拟10.26】雕塑【数论】【容斥】

题目大意:

题目链接:https://jzoj.net/senior/#main/show/3059
n×nn imes nmm个障碍网格中选择nn个格子,使得任意两个格子不在同一行、同一列的方案数。


思路:

由于mm只有1010,可以考虑用总方案数-放在障碍上的方案数。
总方案数很明显是n!n!
因为第一行有nn个格子可以选,选择其中一个后,第二行就有n1n-1个格子可选,第三行就有n2n-2个可选,一直到最后一行只有11个可选。总方案数就是n×(n1)×...×1=n!n imes (n-1) imes ... imes 1=n!
然后我们会发现总方案数里面包含了选择11个障碍的方案数。所以我们再减去选择11个的方案数。此时我们会发现,总方案数里面包含了选择22个方案数,减去选择一个的方案数后,减去的也包含了选择22个障碍的方案数。相抵消,就等于没有减去选择22个障碍的方案数。于是我们就要减去选择22个的方案数。然后加上选择33个,减去选择44个。。。
用人话说,设sis_i表示在n×nn imes n网格中选择nn个格子,其中有ii个格子是障碍的情况数量。那么答案就是
{n!s1+s2s3+s4...sn1+sn(n%2==0)n!s1+s2s3+s4...+sn1sn(n%2==1)left{egin{matrix}n!-s_1+s_2-s_3+s_4-...-s_{n-1}+s_n(n\%2==0) \n!-s_1+s_2-s_3+s_4-...+s_{n-1}-s_n(n\%2==1)end{matrix} ight.
那么如何求si?s_i?
很明显,si=s_i=选择ii个障碍的方案数× imes选择mim-i个非障碍的方案数。
选择ii个障碍的方案数可以用搜索求。因为mm只有1010,可以2m2^m判断是否选择则个障碍。
那么选择mim-i个非障碍的方案数其实就是要我们求在(mi)×(mi)(m-i) imes(m-i)的格子里选择mm个格子的方案数,自然就是m!m!了。
0200sim 20的阶乘要提前预处理,时间复杂度O(m×2m)O(m imes 2^m)


代码:

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;

int n,m,x[15],y[15];
ll ans,num[25],s;
bool used[3][25];

void dfs(int i,int cnt,int sum)
//i表示处理到第i个障碍,cnt表示选择了多少个障碍,sum表示需要选择的总障碍数
{
    if (cnt==sum)  //选择完毕
    {
        s++;  //方案数
        return;
    }
    if (i>m) return;
    if (!used[1][x[i]]&&!used[2][y[i]])  //这一行和这一列都没有被选择
    {
        used[1][x[i]]=used[2][y[i]]=1;
        dfs(i+1,cnt+1,sum);
        used[1][x[i]]=used[2][y[i]]=0;
    }
    dfs(i+1,cnt,sum);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf("%d%d",&x[i],&y[i]);
    num[0]=1;
    for (int i=1;i<=n;i++)  //预处理阶乘
        num[i]=i*num[i-1];
    ans=num[n];
    for (int i=1;i<=m;i++)
    {
        s=0;
        dfs(1,0,i);
        if (!s) break;
        if (i&1) ans-=s*num[n-i];
            else ans+=s*num[n-i]; 
    }
    printf("%lld
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998409.html