【题解】Gnutella Chessmaster(容斥+下降幂+二项式定理)

【题解】Gnutella Chessmaster(容斥+下降幂+二项式定理)

Gnutella Chessmaster

绝世好题!

题目大意:有一个\(n\times n\)的棋盘,现在要在上面放\(k\)个Bishop,每个Bishop打两条对角线,问你放Bishop的方案数,方案不同当且仅当一个位置上存在主教的状态不同。你要对于每个k输出方案。

prop1

对于这个棋盘二分图染色((1,1)=白),显然白色格子上面的Bishop打不到黑色格子上面的Bishop,于是我们可以分开算黑白格子的方案数然后卷积起来。

prop2

我们把白色格子抠出来,然后旋转45度,问题转化为了在上面放中国象棋::车,使得车互不攻击的方案数。

可以发现格子不是一个规整的形状,但是我们可以随意交换两行两列方案数不变。

我们考虑把他变成一个类似阶梯的东西,使得高的在右

然后我们考虑一个简单一点的问题

在\(n\)级阶梯上放\(k\)个中国象棋::车且他们互不攻击的方案书记为\(R_{k}^n\),那么
\[
R_{k}^n={n+1 \brace n-k+1}
\]
证明在最下面的补充里

可以发现,我们变出来的东西,只有最上面是少的,我的意思是,相邻阶梯高度差小于等于2,且不存在大于等于2的连续段,也就是说我们每次需要补的只是某级阶梯最上面那一个格子。补全之后有些地方是禁区,可以容斥。

algo

考虑容斥上面这个东西,考虑在一个禁区(一个阶梯的最上面那个方块)放一个中国象棋::车的话,直接把一行一列消掉并且发现新的形状是一个子问题(仍然是一个阶梯,因为只是消掉了比他高的列,不影响比他小的列),所以考虑一下容斥,枚举放了几个禁区。

那么设\(a_k\)为原问题答案,总共有\(m\)个禁区,那么
\[
a_k=\sum_{i=0}^m (-1)^i{m\choose i}R_{k-i}^{n-i}
\]
代入
\[
a_k=\sum_{i=0}^m(-1)^i{m\choose i}{{n-i+1}\brace {n-k+1}}
\]
化开斯特林数
\[
a_k=\sum_{i=0}^m (-1)^i{m\choose i}\sum_{j=0}^{n-k} {n-k+1\choose j}{1\over (n-k+1)!}(-1)^j (n+1-k-j)^{n-i+1}
\]
最后那一项不好办,但是我们可以直接交换和式
\[
a_k=\sum_{j=0}^{n-k} (-1)^j {1\over j!(n+1-k-j)!}\sum_{i=0}^m (n+1-k-j)^{n-i+1}{m\choose i} (-1)^i
\]
二项式定理
\[
a_k=\sum_{j=0}^m (-1)^j {(n+1-k-j)^{n-m+1}\over j!(n+1-k-j)!}\times (n-k-j)^m
\]

还整理一下
\[
a_k=\sum_{j=0}^{n-k} {(-1)^j\over j!} \times {(n+1-k-j)^{n-m+1}\times (n-k-j)^m\over (n+1-k-j)!}
\]

这个看起来可以NTT了

答案算出来老不对 为什么...?????

一些补充

Rook Factorization定理

在\(n\)级阶梯上放\(k\)个中国象棋::车且,第\(i\)级阶梯高度为\(h_i\),则使得他们互不攻击的放置方案数记为\(R_{k}^n\),那么
\[
\sum_{i} R_{n-i}x^{\underline i} = \prod_i(x-h_i-i+1)
\]
根据组合意义相当于在阶梯下方补格子。代入\(h_i=i\)然后套用斯特林数就行

\(O(n^2)\)代码

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<deque>
#include<assert.h>

using namespace std;  typedef long long ll;
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=1<<19;
const int mod=998244353;
const int g=3;
const int gi=(mod+1)/3;
int jc[maxn],inv[maxn];
int s[5001][5001];
typedef vector<int> poly;

inline int MOD(const int&x){return x>=mod?x-mod:x;}
inline int MOD(const int&x,const int&y){return 1ll*x*y%mod;}
inline int MOD(const vector<int>&ve){int ret=1;for(const auto&t:ve) ret=MOD(t,ret); return ret;}

inline int ksm(const int&ba,const int&p){
    int ret=1;
    for(int t=p,b=ba;t;t>>=1,b=MOD(b,b))
        if(t&1) ret=MOD(ret,b);
    return ret;
}

void NTT(poly&a,const int&tag){
    static int r[maxn];
    int len=a.size();
    for(int t=0;t<len;++t)
        if((r[t]=r[t>>1]>>1|(t&1?len>>1:0))>t)
            swap(a[t],a[r[t]]);
    for(int t=1,wn,s=tag==1?g:gi;t<len;t<<=1){
        wn=ksm(s,(mod-1)/(t<<1));
        for(int i=0;i<len;i+=t<<1)
            for(int j=0,p,w=1;j<t;++j,w=MOD(w,wn))
                p=MOD(a[i+j+t],w),a[i+j+t]=MOD(a[i+j]-p+mod),a[i+j]=MOD(a[i+j]+p);
    }
    if(tag!=1)
        for(int t=0,i=mod-(mod-1)/len;t<len;++t)
            a[t]=MOD(a[t],i);
}

poly operator * (poly a,poly b){
    int t1=a.size()+b.size()-1,len=1;
    while(len<t1) len<<=1;
    a.resize(len); b.resize(len);
    NTT(a,1); NTT(b,1);
    for(int t=0;t<len;++t) a[t]=MOD(a[t],b[t]);
    NTT(a,-1); a.resize(t1);
    return a;
}

void pre(const int&n){
    s[0][0]=1;
    for(int t=1;t<=5000;++t)
        for(int i=1;i<=5000;++i)
            s[t][i]=MOD(s[t-1][i-1]+MOD(i,s[t-1][i]));
    //cerr<<s[3][2]<<' '<<s[3][1]<<' '<<s[5][1]<<endl;
    jc[0]=inv[0]=1;
    for(int t=1;t<=n;++t) jc[t]=MOD(jc[t-1],t);
    inv[n]=ksm(jc[n],mod-2);
    for(int t=n-1;t;--t) inv[t]=MOD(inv[t+1],t+1);
    for(int t=1;t<=n;++t)
        if(MOD(inv[t],jc[t])!=1)
            puts("wa");
}
#define print(s) cerr<<#s" = ",printt(s)

void printt(poly a){
    for(auto t:a) fprintf(stderr,"%d ",t);
    cerr<<'\n';
}

int c(int n,int m){
    if(n<m) return 0;
    return MOD(jc[n],MOD(inv[m],inv[n-m]));
}

poly ans,wh,bl;
poly gen(poly st){
        sort(st.begin(),st.end());
    deque<int> q(st.begin(),st.end());
    int n=1,m=0;
    while(q.size())
        if(q[0]==n) q.pop_front(),++n;
        else if(q[0]==n-1) ++q[0],++m;
        else if(q[0]<n-1) puts("wa");
        else if(q[0]>n) q.push_front(n),++m,assert(n==1);
    --n;
    poly a(n+1),b(n+1),ret(n+1);
    for(int k=1;k<=n;++k)
        for(int i=0;i<=m;++i){
            int temp=MOD(c(m,i),s[n-i+1][n-k+1]);
            if(i&1) temp=mod-temp;
            ret[k]=MOD(ret[k]+temp);
        }
    ret[0]=1;
    return ret;
}

int main(){
    int n=qr(); pre(1e5);
    for(int t=1;t<=n+n-1;++t) (t&1?wh:bl).push_back(n-abs(n-t));
    ans=gen(wh)*gen(bl);
    for(int t=1;t<=n+n-1;++t)
        printf("%d ",ans[t]);
    putchar('\n');
    return 0;
}

原文地址:https://www.cnblogs.com/winlere/p/12163259.html

时间: 2024-10-31 11:23:56

【题解】Gnutella Chessmaster(容斥+下降幂+二项式定理)的相关文章

青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 第一种,树形dp+LCA 比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2 然后找出所有互质的对数,然后对为1的数进行特殊处理.(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7) 对所有的非1质数对,采用离

【题解】分特产(组合数+容斥)

[题解]分特产(组合数+容斥) 一道小水题. 假如没有这个要求每个人都要有一个特产的限制我们直接可以组合数. 我们又发现人(本质上)是没有区别的,所以容斥的复杂度只有\(O(n)\) \(n\)个人分\(m\)个特产,每个特产有\(a_i\)个,人可以不拿特产,的方案数就是把\(a_i\)分成\(n\)份,而且可以分为\(0\)份. 这个的答案就是 \[ f(n)=\prod_{i=1}^m {a_i+n-1\choose n-1} \] 意思就是有\(a_i+n\)个球分成不为空\(n\)份的

【题解】毒蛇越狱(FWT+容斥)

[题解]毒蛇越狱(FWT+容斥) 问了一下大家咋做也没听懂,按兵不动没去看题解,虽然已经晓得复杂度了....最后感觉也不难 用FWT_OR和FWT_AND做一半分别求出超集和和子集和,然后 枚举问号是01,裸的,\(O(2^{cnt[?]})\) 默认问号是1,利用子集和求,\(O(2^{cnt[1]})\) 默认问号是0,利用超集和求,\(O(2^{cnt[0]})\) 可以知道\(min(cnt)\le n/3\),所以复杂度\(O(n2^n 2^{n/3}Q)\) //@winlere #

HDU 4135 Co-prime(容斥:二进制解法)题解

题意:给出[a,b]区间内与n互质的个数 思路:如果n比较小,我们可以用欧拉函数解决,但是n有1e9.要求区间内互质,我们可以先求前缀内互质个数,即[1,b]内与n互质,求互质,可以转化为求不互质,也就是有除1的公因数.那么我们把n质因数分解,就能算出含某些公因数的不互质的个数.因为会重复,所以容斥解决.因为因数个数可能很多(随便算了一个20!> 2e18,所以质因数分解个数不会超过20个),我们可以用二进制来遍历解决. #include<set> #include<map>

HDU 2841 Visible Trees(容斥)题解

题意:有一块(1,1)到(m,n)的地,从(0,0)看能看到几块(如果两块地到看的地方三点一线,后面的地都看不到). 思路:一开始是想不到容斥...后来发现被遮住的地都有一个特点,若(a,b)有gcd(a,b)!= 1,那么就会被遮住.因为斜率k一样,后面的点会被遮住,如果有gcd,那么除一下就会变成gcd = 1的那个点的斜率了.所以问题转化为求gcd不为1有几个点,固定一个点,然后容斥. #include<set> #include<map> #include<queue

题解报告:hdu 4135 Co-prime(容斥定理入门)

Problem Description Given a number N, you are asked to count the number of integers between A and B inclusive which are relatively prime to N.Two integers are said to be co-prime or relatively prime if they have no common positive divisors other than

FJUT3565 最大公约数之和(容斥)题解

题意:给n,m,求出 思路:题意为求出1~m所有数和n的gcd之和.显然gcd为n的因数.我们都知道gcd(a,b)= c,那么gcd(a/c,b/c)= 1.也就是说我们枚举n所有的因数k,然后去找1~m/k中和n/k互质的个数就是gcd为k的个数.这个直接容斥就行. 代码: #include<iostream> #include<algorithm> #include<cstdio> #include<stdio.h> #include<strin

[arc102E]Stop. Otherwise...[容斥+二项式定理]

题意 你 \(n\) 个完全相同骰子,每个骰子有 \(k\) 个面,分别标有 \(1\) 到 \(k\) 的所有整数.对于\([2,2k]\) 中的每一个数 \(x\) 求出有多少种方案满足任意两个骰子的和都不为 \(x\) 的方案数. 分析 对于每个 \(x\) ,我们需要单独考虑当 \(i\le x\) 时, \(i\) 和 \(x-i\) 只能出现一个.我们将他们看成同一种权值,数量记为 \(w\) ,剩余权值数量记位 \(cnt\) ,然后枚举有多少种特殊权值没出现 (\(ans\))

HDU 5321 Beautiful Set 容斥 (看题解)

HDU 5321 感觉有点抗拒这种题目, 看到就感觉自己不会写,其实就是个沙雕题, 感觉得找个时间练练这种题. g[ i ] 表示gcd为 i 的倍数的方案数, f[ i ] 表示gcd为 i 的方案数, 然后先算g[ i ]然后直接容斥. #pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> #define LL long long #define LD long double #define ull