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

版权声明:若希望转载,在评论里直接说明即可,谢谢! https://blog.csdn.net/SSL_ZYC/article/details/85851349

题目大意:

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


思路:

由于 m m 只有 10 10 ,可以考虑用总方案数 - 放在障碍上的方案数。
总方案数很明显是 n ! n!
因为第一行有 n n 个格子可以选,选择其中一个后,第二行就有 n 1 n-1 个格子可选,第三行就有 n 2 n-2 个可选,一直到最后一行只有 1 1 个可选。总方案数就是 n × ( n 1 ) × . . . × 1 = n ! n\times (n-1)\times ...\times 1=n!
然后我们会发现总方案数里面包含了选择 1 1 个障碍的方案数。所以我们再减去选择 1 1 个的方案数。此时我们会发现,总方案数里面包含了选择 2 2 个方案数,减去选择一个的方案数后,减去的也包含了选择 2 2 个障碍的方案数。相抵消,就等于没有减去选择 2 2 个障碍的方案数。于是我们就要减去选择 2 2 个的方案数。然后加上选择 3 3 个,减去选择 4 4 个。。。
用人话说,设 s i s_i 表示在 n × n n\times n 网格中选择 n n 个格子,其中有 i i 个格子是障碍的情况数量。那么答案就是
{ 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 ) \left\{\begin{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}\right.
那么如何求 s i ? s_i?
很明显, s i = s_i= 选择 i i 个障碍的方案数 × \times 选择 m i m-i 个非障碍的方案数。
选择 i i 个障碍的方案数可以用搜索求。因为 m m 只有 10 10 ,可以 2 m 2^m 判断是否选择则个障碍。
那么选择 m i m-i 个非障碍的方案数其实就是要我们求在 ( m i ) × ( m i ) (m-i)\times(m-i) 的格子里选择 m m 个格子的方案数,自然就是 m ! m! 了。
0 20 0\sim 20 的阶乘要提前预处理,时间复杂度 O ( m × 2 m ) O(m\times 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\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SSL_ZYC/article/details/85851349
今日推荐