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

题目大意:

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


思路:

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


代码:

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值