【训练题60:容斥 | 计数】Placing Rooks | CF 1342E

该博客探讨了一个n×n的棋盘上放置n个车的问题,要求每个格子都能被车攻击到,并有k对车可以相互攻击。提出了两种基本方案:每行或每列各放一个车。当k不为0时,两种方案不同。通过组合数学和容斥原理,计算了当k大于0时的放置方案数,涉及到了排列组合和二项式定理的应用。代码部分展示了如何用C++实现该计算。
摘要由CSDN通过智能技术生成

题意

  • Placing Rooks | CF 1342E
    一个 n × n n\times n n×n 的棋盘放 n n n 个车,攻击范围为十字
    让每一个格子都能被打倒,且有 k k k 对车之间可以相互攻击到
    求方案数
  • 1 ≤ n ≤ 200000 1\le n\le 200000 1n200000
    1 ≤ k ≤ n ( n − 1 ) 2 1\le k\le \frac{n(n-1)}{2} 1k2n(n1)

思路

  • 因为是 n × n n\times n n×n 的棋盘且 n n n 个车且每个格子都要被攻击到
    有两种方案,一种每一行都有一个车,一种每一列都有一个车
    证明:假设有一行都为空,那么每一列必须至少有一个车让这一行被攻击到,反之也是。
    只有当 k ≠ 0 k\ne0 k=0 时这两种方案是不同的。 此时方案数需 × 2 \times2 ×2
    所以我们暂时先考虑 k > 0 k>0 k>0 的情况
  • 假设每一行都放一个车。
    因为有 k k k 对车之间可以相互攻击到,那肯定是对于某些列有多个车
    假设我们 n n n 个车都放在第一列,那么就有 n − 1 n-1 n1 对车能相互攻击
    但是我们的 k < n − 1 k<n-1 k<n1,我们就拿第一列的某辆车,挪到那行的另一没有车的列,就能让互相攻击的对数 − 1 -1 1
    也就是说,我们有 k k k 对相互攻击的车,意味着就有 k k k 列是空着的,没有车
  • 首先我们需要选出 k k k 列,让他们空着,方案数就是 C n k C_n^k Cnk
    接下来的问题就是把 n n n 个棋子放进这 n − k n-k nk 列,且每一列都至少有一个棋子,求方案数了
    这个就是一个容斥 的问题了
    全集,就是随便放,就是 ( n − k ) n (n-k)^n (nk)n
    或者是空一列,就是 ( n − k − 1 ) n C n − k 1 (n-k-1)^n C_{n-k}^1 (nk1)nCnk1
    或者是空两列,就是 ( n − k − 2 ) n C n − k 2 (n-k-2)^n C_{n-k}^2 (nk2)nCnk2
    ⋯ \cdots
    然后对于空奇数列,我们减去;对于空偶数列,我们相加,最后的答案就是:
    ∑ i = 0 n − k ( − 1 ) i ( n − k − i ) n C n − k i \sum_{i=0}^{n-k}(-1)^i(n-k-i)^nC_{n-k}^i i=0nk(1)i(nki)nCnki
  • 其实本题抽象化,就是 n n n 个不同小球放到 m m m 个不同盒子,要求每个盒子都非空的方案数
    可以看到,若预处理后,我们时间复杂度为 O ( ( n − k ) log ⁡ n ) O((n-k)\log n) O((nk)logn)
    k k k 其实最大就是取到 n − 1 n-1 n1,不然就是非法情况

代码

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 2e5+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

ll fac[MAX];
ll ivfac[MAX];

void init(int n){
    fac[0] = 1;
    for(int i = 1;i <= n;++i)fac[i] = fac[i-1] * i % MOD;
    ivfac[n] = inv(fac[n]);
    for(int i = n - 1;~i;--i)ivfac[i] = ivfac[i+1] * (i+1) % MOD;
}
ll C(int n,int m){
    if(m < 0 || m > n)return 0;
    return fac[n] * ivfac[m] % MOD * ivfac[n - m] % MOD;
}
int main()
{
    init(200000);
    int n;ll k;cin >> n >> k;
    ll ans = 0;
    for(int i = 0;i <= n - k;++i){
        ll pm = (i&1) ? -1 : 1;
        ans = (ans + pm * qpow(n-k-i,n) % MOD * C(n-k,i) % MOD + MOD) % MOD;
    }
    ans = ans * C(n,k) % MOD;
    if(k)ans = ans * 2 % MOD;
    cout << ans;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值