AtCoder Beginner Contest 327题解(F,G)

abc327题解

F - Apples

题意:给定二维平面上的N个点坐标和一个大小为D*W的矩形框,问这个框最多可以框住多少个点。

解答:考虑一个点(X,Y)能够被框住当且仅当框的左下角处于 [ X − D , X ] × [ Y − W , Y ] [X-D,X]\times[Y-W,Y] [XD,X]×[YW,Y]区域内。在y轴上建立线段树,按照x坐标从小到大顺序依次加入点(X,Y),即[Y-W,Y]区间加1,同时将x坐标已经离开区域的点(X,Y)删去,即[Y-W,Y]区间减1,这个操作相当于从左往右移动框,每次移动后查询区间最大值,更新答案。

G - Many Good Tuple Problems

题意:N个点的图,要赋予它M条边(边带标号,同时每条边区分起点和终点),要求加完边后不存在奇圈,问有多少种加边方案。

解答:即数有多少个N个点M条边(均带标号,可有重边)的二分图(记为A(N,M))然后乘以 2 M 2^M 2M(因为每条边区分起终点)即可。 A ( N , M ) = ∑ k = 1 m a x E d g e f ( N , k ) × b ( M , k ) A(N,M)=\sum_{k=1}^{maxEdge}f(N,k)\times b(M,k) A(N,M)=k=1maxEdgef(N,k)×b(M,k) m a x E d g e = n 2 / 4 maxEdge=n^2/4 maxEdge=n2/4,f(N,k)为N个带标号点和k个无标号边简单二分图个数,b(M,k)为将M个带标号边分成k个不同集合的方案数,即为 k ! { M k } k!{M\brace k} k!{kM},第二类斯特林数 { M k } {M\brace k} {kM}公式为 S k = 1 k ! ∑ i = 0 k ( − 1 ) k − i ( k i ) i M S_k=\frac{1}{k!}\sum_{i=0}^{k}(-1)^{k-i}\binom{k}{i}i^M Sk=k!1i=0k(1)ki(ik)iM(可通过二项式反演得到)。

记g(N,k)是N个点k个边的染色二分图个数,即 g ( N , k ) = ∑ i = 1 N − 1 ( N i ) ( i ∗ ( N − i ) k ) g(N,k)=\sum_{i=1}^{N-1}\binom{N}{i}\binom{i*(N-i)}{k} g(N,k)=i=1N1(iN)(ki(Ni)),而连通染色二分图个数 h ( N , k ) = g ( N , k ) − ∑ i = 0 N − 1 ∑ j = 0 k ( N − 1 i − 1 ) h ( i , j ) g ( N − i , k − j ) h(N,k)=g(N,k)-\sum_{i=0}^{N-1}\sum_{j=0}^{k}\binom{N-1}{i-1}h(i,j)g(N-i,k-j) h(N,k)=g(N,k)i=0N1j=0k(i1N1)h(i,j)g(Ni,kj)(容斥原理,枚举第一个顶点所在连通分量的大小),则连通二分图个数为h(N,k)/2。

于是 f ( N , k ) = h ( N , k ) + ∑ i = 0 N − 1 ∑ j = 0 k ( N − 1 i − 1 ) h ( i , j ) f ( N − i , k − j ) f(N,k)=h(N,k)+\sum_{i=0}^{N-1}\sum_{j=0}^{k}\binom{N-1}{i-1}h(i,j)f(N-i,k-j) f(N,k)=h(N,k)+i=0N1j=0k(i1N1)h(i,j)f(Ni,kj)(枚举第一个顶点所在连通分量大小)
代码:

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
template<typename T>
constexpr T power(T a,i64 b){
    T ans=1;
    for(;b;b/=2){
        if(b%2==1){
            ans*=a;
        }
        a*=a;
    }
    return ans;
}
template<int P>
struct MInt{
    constexpr MInt(): x{} {}
    constexpr MInt(i64 x): x{norm(x%P)} {}
    int x;
    constexpr int norm(int x) const{
        if(x<0){
            return x+P;
        }
        return x;
    }
    constexpr int val() const{
        return x;
    }
    constexpr MInt inv() const{
        return power(*this,P-2);
    }
    constexpr MInt operator-() const{
        MInt res;
        res.x=P-x;
        return res;
    }
    constexpr MInt& operator+=(const MInt& rhs){
        x=(x+rhs.x)%P;
        return *this;
    }
    constexpr MInt& operator-=(const MInt& rhs){
        x=norm(x-rhs.x);
        return *this;
    }
    constexpr MInt& operator*=(const MInt& rhs){
        x=(1ll*x*rhs.x)%P;
        return *this;
    }
    constexpr MInt& operator/=(const MInt& rhs){
        return *this*=rhs.inv();
    }
    friend constexpr MInt operator+(const MInt& lhs,const MInt& rhs){
        MInt res=lhs;
        res+=rhs;
        return res;
    }
    friend constexpr MInt operator-(const MInt& lhs,const MInt& rhs){
        MInt res=lhs;
        res-=rhs;
        return res;
    }
    friend constexpr MInt operator*(const MInt& lhs,const MInt& rhs){
        MInt res=lhs;
        res*=rhs;
        return res;
    }
    friend constexpr MInt operator/(const MInt& lhs,const MInt& rhs){
        MInt res=lhs;
        res/=rhs;
        return res;
    }
    friend std::istream& operator>>(std::istream& is,MInt& a){
        i64 v;
        is>>v;
        a=MInt(v);
        return is;
    }
    friend std::ostream& operator<<(std::ostream& os,const MInt& a){
        os<<a.val();
        return os;
    }
    friend constexpr bool operator==(const MInt& lhs,const MInt& rhs){
        return lhs.x==rhs.x;
    }
    friend constexpr bool operator!=(const MInt& lhs,const MInt& rhs){
        return lhs.x!=rhs.x;
    }
};
constexpr int P = 998244353;
using Z = MInt<P>;
struct Comb{
    Comb():n{0},_fac{1},_invfac{1} {}
    Comb(int m):Comb(){
        init(m);
    }
    void init(int m){
        m=min(m,P-1);
        if(n>=m) return;
        _fac.resize(m+1);
        _invfac.resize(m+1);
        _inv.resize(m+1);
        for(int i=n+1;i<=m;i++){
            _fac[i]=_fac[i-1]*i;
        }
        _invfac[m]=_fac[m].inv();
        for(int i=m;i>n;i--){
            _invfac[i-1]=_invfac[i]*i;
            _inv[i]=_invfac[i]*_fac[i-1];
        }
        n=m;
        return;
    }
    int n;
    vector<Z> _fac,_invfac,_inv;
    Z fac(int m){
        if(m>n) init(2*m);
        return _fac[m];
    }
    Z invfac(int m){
        if(m>n) init(2*m);
        return _invfac[m];
    }
    Z inv(int m){
        if(m>n) init(2*m);
        return _inv[m];
    }
    Z binom(int n,int m){
        if(n<m||m<0) return 0;
        return fac(n)*invfac(m)*invfac(n-m);
    }
}comb;
int main(){
    int n,m;
    cin>>n>>m;
    const int maxEdge=n*n/4+1;
    vector<Z> b(maxEdge+1,0);
    for(int i=1;i<=maxEdge;i++){
        for(int j=0;j<=i;j++){
            b[i]+=((i-j)%2?-1:1)*comb.binom(i,j)*power(Z(j),m);
        }
    }
    vector<vector<Z>> g(n+1,vector<Z>(maxEdge+1,0));
    for(int i=0;i<=n;i++){
        for(int j=0;j<=maxEdge;j++){
            for(int k=0;k<=i;k++){
                g[i][j]+=comb.binom(i,k)*comb.binom(k*(i-k),j);
            }  
        }
    }
    vector<vector<Z>> h(n+1,vector<Z>(maxEdge+1,0));
    for(int i=0;i<=n;i++){
        for(int j=0;j<=maxEdge;j++){
            h[i][j]+=g[i][j];
            for(int k=1;k<i;k++){
                for(int l=0;l<=j;l++){
                    h[i][j]-=comb.binom(i-1,k-1)*h[k][l]*g[i-k][j-l];
                }
            }
        }
    }
    vector<vector<Z>> f(n+1,vector<Z>(maxEdge+1,0));
    for(int i=0;i<=n;i++){
        for(int j=0;j<=maxEdge;j++){
            f[i][j]+=h[i][j]/2;
            for(int k=1;k<i;k++){
                for(int l=0;l<=j;l++){
                    f[i][j]+=comb.binom(i-1,k-1)*h[k][l]/2*f[i-k][j-l];
                }
            }
        }
    }
    Z ans=0;
    for(int j=0;j<=maxEdge;j++){
        ans+=f[n][j]*b[j]*power(Z(2),m);
    }
    cout<<ans<<'\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值