八月刷题记录2

19 篇文章 0 订阅
11 篇文章 0 订阅
本文介绍了解决洛谷P4551最长异或路径问题的方法,通过构建01trie数据结构,维护每个节点到根的异或值,并利用高位查找策略贪心地选择异或值最大的路径。关键在于理解路径异或的性质和trie的高效查询。
摘要由CSDN通过智能技术生成

洛谷P4551 最长异或路径

给定一棵 n 个点的带权树,结点下标从1开始到 n。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
01trie板子
任意指定一个点为根。建一棵01trie,这棵trie存所有点到根的路径的异或值。
考虑到路径(u,v)的异或=路径(root,u)^(root,v),因此对于每一个点到根的异或值,我们都在01trie上找到另一个点到根的异或值,满足两者异或最大。维护这个最大值。
如何找?高位往下找,能不同就贪心的取不同。因为这样异或值一定尽可能大。

//
// Created by artist on 2021/8/17.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}
const int maxn = 1e5+5;
vector<pair<int,int> > G[maxn];

int rt[maxn],tot,pos=1,len,trie[maxn<<1][3];

// 计算出每个节点到根的异或和
void dfs(int u,int xorr,int fa){
    rt[++tot]=xorr;
    for(pair<int,int> v:G[u]){
        if(v.first==fa) continue;
        dfs(v.first,xorr^v.second,u);
    }
}

void Insert(int x){
    int p=0;
    for(int i=len;~i;--i){
        int n=(x>>i)&1;
        if(trie[p][n]==0)
            trie[p][n]=pos++;
        p=trie[p][n];
    }
}

signed main() {
    int n;scanf("%d",&n);
    int mx=0;
    for(int i=1,u,v,w;i<=n-1;++i){
        scanf("%d%d%d",&u,&v,&w);
        G[u].pb(mkp(v,w));
        G[v].pb(mkp(u,w));
        mx=max(mx,w);
    }
    len=__lg(mx)+1; // ==3(三位)(0,1,2)
    // 预处理出所有(root,u)
    dfs(1,0,0);

    // 建01trie
    for(int i=1;i<=tot;++i){
        Insert(rt[i]);
    }

    ll ans=0;
    // 对每一个节点,都在trie找到那个使得他们异或起来最大的和
    for(int i=1;i<=n;++i){
        int p1=0,p2=0;
        ll tmp=0;
        for(int j=len;~j;--j){
            int k=(rt[i]>>j)&1;
            if(trie[p2][k^1])
                p2=trie[p2][k^1],tmp|=(ll)(1<<j);
            else p2=trie[p2][k];
            p1=trie[p1][k];
        }
        ans=max(ans,tmp);
    }
    printf("%d\n",ans);
}

CF 683 E Xor Tree

题意:n个点,点有点权。两点间有边当且只当其中一点是另一点在所有点(除自己以外)中异或和最小的点。重边算一条边。问最少删多少个点使得该图是树。
01trie上dp+正难则反(答案问的是最少删除多少,统计的是最多取多少)。

//
// Created by artist on 2021/8/17.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}
const int maxn = 2e5+5;
int a[maxn];
int trie[maxn<<4][3],sz[maxn<<4];
int len,cnt;

void Insert(int x){
    int p=0;
    for(int i=len;~i;--i){
        int n=(x>>i)&1;
        if(!trie[p][n])
            trie[p][n]=++cnt;
        p=trie[p][n];
        sz[p]++;
    }
}

int dfs(int u){
    if(sz[u]==1) return 1; // 叶子
    if(!trie[u][0]) return dfs(trie[u][1]);
    if(!trie[u][1]) return dfs(trie[u][0]);
    if(sz[trie[u][0]]>=2&&sz[trie[u][1]]>=2) return max(dfs(trie[u][0]),dfs(trie[u][1]))+1;
    return dfs(trie[u][0])+dfs(trie[u][1]);
}

signed main() {
    int n;scanf("%d",&n);
    int mx=0;
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        mx=max(mx,a[i]);
    }
    len=__lg(mx);
    for(int i=1;i<=n;++i){
        Insert(a[i]);
    }
    // dfs回溯处理
    printf("%d\n",n-dfs(0));
}

洛谷P6018 [Ynoi2010] Fusion tree

做的第一道由乃题。
01trie处理全局+1和全局异或和。
建多棵trie。
lazy tag缓存修改。

//
// Created by artist on 2021/8/17.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}
const int maxn = 5e5+5;
const int maxh = 21;
vector<int> G[maxn];
int f[maxn],a[maxn],trie[maxn*23][2],w[maxn*23],xorv[maxn*23],lzy[maxn];
int rt[maxn],cnt;   // 根节点

void dfs(int u,int fa){
    f[u]=fa;
    for(int v:G[u]){
        if(v==fa) continue;
        dfs(v,u);
    }
}

int mknode() {
    ++cnt;
    trie[cnt][1] = trie[cnt][0] = w[cnt] = xorv[cnt] = 0;
    return cnt;
}

void maintain(int o){
    w[o] = xorv[o] = 0;
    if(trie[o][0]){
        w[o] += w[trie[o][0]];
        xorv[o] ^= xorv[trie[o][0]]<<1;
    }
    if(trie[o][1]){
        w[o] += w[trie[o][1]];
        xorv[o] ^= (xorv[trie[o][1]]<<1) | (w[trie[o][1]] & 1);
    }
    w[o] = w[o] & 1; // 只存奇偶性(删掉这句话也可以,反正有效的只有奇偶性)
}

// 从低位到高位建树
void insert(int &o,int x,int dp){
    if(!o) o = mknode();
    if(dp>maxh) return (void)(w[o]++);
    insert(trie[o][x&1],x>>1,dp+1);
    maintain(o);
}

// 删了,但是路径都在。只有w变成了0
void erase(int o,int x,int dp){
    if(dp>maxh) return (void)(w[o]--);
    erase(trie[o][x&1],x>>1,dp+1);
    maintain(o);
}

void add1(int o){
    swap(trie[o][1],trie[o][0]);
    if(trie[o][0]) add1(trie[o][0]);
    maintain(o);
}

// lazy tag 修改
int get(int x){
    return (f[x]==-1?0:lzy[f[x]])+a[x];
}

signed main() {
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        G[u].pb(v);
        G[v].pb(u);
    }
    G[0].pb(1);
    // get father
    dfs(1,-1);
    // 建trie
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        if(~f[i]) insert(rt[f[i]],a[i],0);
    }
    for(int i=1,opt,x,y;i<=m;++i){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d",&x);
            lzy[x]++;
            add1(rt[x]); // 每个点的数据仅存在于其根节点的那棵树中
            if(x==1) continue;
            if(~f[f[x]]) erase(rt[f[f[x]]],get(f[x]),0);
            a[f[x]]++;
            if(~f[f[x]]) insert(rt[f[f[x]]],get(f[x]),0); // 其根节点要特殊修改(在其根节点的根节点处)
        }else if(opt==2){
            scanf("%d%d",&x,&y);
            if(x!=1) erase(rt[f[x]],get(x),0); // 暴力修改
            a[x]-=y;
            if(x!=1) insert(rt[f[x]],get(x),0);
        }else{
            scanf("%d",&x);
            printf("%d\n",xorv[rt[x]]^get(f[x]));
        }
    }
}

ICPC Mid-Central USA Region 2019 Commemorative Race

题意:给一张有向无环图。许多选手都选择最长路走(可能最长路有很多条),要求删掉至多一条边,走到被删的边的一点的选手不得不换路径走。要求选手能走的最长的路最短。
这道题挺有趣。
首先我们考虑把所有最长路径找出来。选手们一定在这上面走。观察发现,当我们把所有最长路径的公共边找出来,每次尝试删除这些边,再于边的起点处跑一次最长路,取最短的删法即是答案。
显然这个时间复杂度不合理,且有许多可以优化的性质没有用上。
因为最长路径的起点很多,我们先建立一个新的点,作为统一的起点,连向所有的点。
我们计算从每个点出发,到达出点的最长路和次长路是多长。
因为删除最长路上的一条边,其实就是从该边起点走次长路。
答案就直接以最长路的路径走到当前点的距离+该点到出点的次长路。即删该边的贡献。
注意到,当一个点往外有分叉时,这条边不能被贡献。
于是我们统计每个边在最长路上出现的次数。
仅仅出现一次的贡献答案即可。
最后减去一因为我们建多了一个点。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
vector<int> G[maxn];

// 最长路和次长路
int mxlen[maxn],mxlen2[maxn];
int vis[maxn],cnt[maxn];
int ans[maxn];

int dfs1(int x){
    if(vis[x]) return mxlen[x];
    vis[x]=1;
    int res = 0;
    for(auto y:G[x]){
        res = dfs1(y) + 1;
        if(res>mxlen[x]){
            mxlen2[x]=mxlen[x];
            mxlen[x]=res;
        }else if(res>mxlen2[x]){
            mxlen2[x]=res;
        }
    }
    return mxlen[x];
}

void dfs2(int x,int len){
    if(vis[x]==2) return;
    vis[x]=2;
    for(auto y:G[x]){
        if(mxlen[y]+1==mxlen[x]){
            // 最长边
            dfs2(y,len+1);
            cnt[len]++;
        }
    }
    ans[len]=mxlen2[x]+len;
}

signed main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v);
    for(int i=1;i<=n;++i) G[0].push_back(i);
    dfs1(0);
    dfs2(0,0);
    int res = mxlen[0];
    for(int i=1;i<=n;++i){
        if(cnt[i]==1) res = min(res,ans[i]);
    }
    printf("%d\n",res-1);
}

ICPC Mid-Central USA Region 2019 Sum and Product

题意:给一个序列,问有多少个区间,区间乘积等于加和。n=2e5。
鸽巢发现只有1特殊,会贡献加和但不贡献乘积。满足条件的区间的非1数个数不超过30个。
枚举区间右端,缩1,向左跳。

//
// Created by artist on 2021/8/19.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}
const int maxn = 2e5+5;
int lst[maxn],a[maxn]; // 当前位置上一个非1的位置

signed main() {
    int n;scanf("%d",&n);
    for(int i=1,l=0;i<=n;++i){
        scanf("%d",&a[i]);
        lst[i]=l;
        if(a[i]!=1) l=i;
    }
    int ans=0;
    // 枚举右端点
    for(int i=1;i<=n;++i){
        ll sm=0,prod=1;
        int j=i;
        while(j){
            sm += a[j];
            prod *= a[j];
            if(prod>=1e8) break;
            if(sm<=prod && sm+j-lst[j]-1>=prod) {
                ans++;
            }
            sm+=j-lst[j]-1; // 1的个数
            j=lst[j];
        }
    }
    printf("%d\n",ans-n);
}

2021CCPC华为云挑战赛1006 仓颉造数

在这里插入图片描述
在这里插入图片描述
对于已有的 2 k 2^k 2k 个数,设为a1,a2,…, a 2 k a_{2^k} a2k,先两两平均,得到 a 1 + a 2 2 , a 3 + a 4 2 , . . . , a 2 k − 1 + a 2 k 2 \frac{a_1+a_2}{2},\frac{a_3+a_4}{2},...,\frac{a_{2^k-1}+a_{2^k}}{2} 2a1+a2,2a3+a4,...,2a2k1+a2k,再两两平均,化开后可以发现得到
在这里插入图片描述
∑ a i 2 k \frac{\sum a_i}{2^k} 2kai
a个 a b \frac{a}{b} ba和b个 b a \frac{b}{a} ab,共a+b( 2 k 2^k 2k)个数,他们进行操作后可达1.

代码略

2021牛客多校2 B - Cannon

这个sb本来还在用ntt冲反正一定会TLE
子问题1

2 k ∑ k ! i ! ( k − i ) ! n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! 2^k\sum \frac{k!}{i!(k-i)!}\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} 2ki!(ki)!k!(ni)!n!(m(ki))!m!

= 2 k k ! ∑ ( n i ) ( m k − i ) =2^k k!\sum \binom{n}{i}\binom{m}{k-i} =2kk!(in)(kim)

= 2 k k ! ( n + m k ) =2^k k!\binom{n+m}{k} =2kk!(kn+m)(*)

子问题2

2 k ∑ n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! 2^k\sum \frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} 2k(ni)!n!(m(ki))!m!

= 2 k ∑ n ! m ! ( n + m − k ) ! ( n + m − k ) ! ( n − i ) ! ( m − ( k − i ) ) ! =2^k \sum \frac{n!m!}{(n+m-k)!}\frac{(n+m-k)!}{(n-i)!(m-(k-i))!} =2k(n+mk)!n!m!(ni)!(m(ki))!(n+mk)!

= 2 k n ! m ! ( n + m − k ) ! ∑ ( n + m − k ) ! ( n − i ) ! ( m − ( k − i ) ) ! =2^k \frac{n!m!}{(n+m-k)!}\sum \frac{(n+m-k)!}{(n-i)!(m-(k-i))!} =2k(n+mk)!n!m!(ni)!(m(ki))!(n+mk)!

= 2 k n ! m ! ( n + m − k ) ! ∑ n − k n ( n + m − k ) ! i ! ( n + m − k − i ) ! =2^k \frac{n!m!}{(n+m-k)!}\sum_{n-k}^n \frac{(n+m-k)!}{i!(n+m-k-i)!} =2k(n+mk)!n!m!nkni!(n+mki)!(n+mk)!(*变换枚举下标)

= 2 k n ! m ! ( n + m − k ) ! ∑ n − k n ( n + m − k i ) =2^k \frac{n!m!}{(n+m-k)!}\sum_{n-k}^n \binom{n+m-k}{i} =2k(n+mk)!n!m!nkn(in+mk)

注意到, ∑ n − k n ( n + m − k i ) = ∑ n ( n + m − k i ) − ∑ n − k − 1 ( n + m − k i ) \sum_{n-k}^{n}\binom{n+m-k}{i}=\sum^{n}\binom{n+m-k}{i}-\sum^{n-k-1}\binom{n+m-k}{i} nkn(in+mk)=n(in+mk)nk1(in+mk),表现为组合数前缀和的形式。

考虑化简

∑ 0 ≤ i ≤ n ( n + m − k i ) − ∑ 0 ≤ i ≤ n − k − 1 ( n + m − k i ) \sum_{0\leq i\leq n}\binom{n+m-k}{i}-\sum_{0\leq i\leq n-k-1}\binom{n+m-k}{i} 0in(in+mk)0ink1(in+mk)

= ∑ 0 ≤ i ≤ n ( n + m − k i ) − ∑ 0 ≤ i ≤ n − k − 1 ( n + m − k n + m − k − i ) =\sum_{0\leq i\leq n}\binom{n+m-k}{i}-\sum_{0\leq i\leq n-k-1}\binom{n+m-k}{n+m-k-i} =0in(in+mk)0ink1(n+mkin+mk)

= ∑ 0 ≤ i ≤ n ( n + m − k i ) − ∑ m + 1 ≤ i ≤ n + m − k ( n + m − k i ) =\sum_{0\leq i\leq n}\binom{n+m-k}{i}-\sum_{m+1\leq i\leq n+m-k}\binom{n+m-k}{i} =0in(in+mk)m+1in+mk(in+mk)

= ∑ 0 ≤ i ≤ n ( n + m − k i ) − ( ∑ 0 ≤ i ≤ n + m − k ( n + m − k i ) − ∑ 0 ≤ i ≤ m ( n + m − k i ) ) =\sum_{0\leq i\leq n}\binom{n+m-k}{i}-(\sum_{0\leq i\leq n+m-k}\binom{n+m-k}{i}-\sum_{0\leq i\leq m}\binom{n+m-k}{i}) =0in(in+mk)(0in+mk(in+mk)0im(in+mk))

= ∑ 0 ≤ i ≤ n ( n + m − k i ) + ∑ 0 ≤ i ≤ m ( n + m − k i ) − 2 n + m − k =\sum_{0\leq i\leq n}\binom{n+m-k}{i}+\sum_{0\leq i\leq m}\binom{n+m-k}{i}-2^{n+m-k} =0in(in+mk)+0im(in+mk)2n+mk 设为 s u m [ n + m − k ] sum[n+m-k] sum[n+mk]

对于连续的k,组合数前缀和可以进行递推。

考虑设 S ( n , m ) = ∑ i = 0 m ( n i ) S(n,m)=\sum_{i=0}^{m}\binom{n}{i} S(n,m)=i=0m(in)

有以下trick:

S ( n , m + 1 ) = S ( n , m ) + ( n m + 1 ) S(n,m+1)=S(n,m)+\binom{n}{m+1} S(n,m+1)=S(n,m)+(m+1n) S ( n , m − 1 ) = S ( n , m ) − ( n m ) S(n,m-1)=S(n,m)-\binom{n}{m} S(n,m1)=S(n,m)(mn)

S ( n + 1 , m ) = ∑ i = 0 m ( n + 1 i ) = ∑ i = 0 m ( n i ) + ( n i − 1 ) = 2 S ( n , m ) − ( n m ) S(n+1,m)=\sum_{i=0}^m \binom{n+1}{i}=\sum_{i=0}^m\binom{n}{i}+\binom{n}{i-1}=2S(n,m)-\binom{n}{m} S(n+1,m)=i=0m(in+1)=i=0m(in)+(i1n)=2S(n,m)(mn)

因此,我们可以递推出 s u m sum sum

结束。

//
// Created by artist on 2021/8/24.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}
const int maxn = 5e6+5;
const int mod = 1e9+9;

int fac[maxn<<1],invf[maxn<<1];

int qpow(int a,int n){
    ll ans=1;
    while(n){
        if(n&1) ans=1ll*a*ans%mod;
        a=1ll*a*a%mod;
        n>>=1;
    }
    return ans;
}

void init(int n,int m){
    fac[0]=1;
    for(int i=1;i<=n+m+5;++i) fac[i]=1ll*fac[i-1]*i%mod;
    invf[n+m+5]=qpow(fac[n+m+5],mod-2);
    for(int i=n+m+4;i>=0;--i) invf[i]=1ll*invf[i+1]*(i+1)%mod;
}

int C(int n,int m){
    if(n<m||m<0) return 0;
    return 1ll*fac[n]*invf[m]%mod*invf[n-m]%mod;
}

int S[maxn<<1];

signed main() {
    int x,y;scanf("%d%d",&x,&y);
    x-=2,y-=2;
    init(x,y);
    int k2=1,ans1=0,ans2=0;

    S[0]=1;
    for(int i=1;i<=x+y;++i){
        S[i]=2ll*S[i-1]%mod;
        S[i]=(1ll*S[i]-C(i-1,x)+mod)%mod;
        S[i]=(1ll*S[i]-C(i-1,y)+mod)%mod;
    }

    for(int i=0;i<=x+y;++i){
        ans1 ^= (1ll*k2*fac[i]%mod*C(x+y,i))%mod;
        ans2 ^= (1ll*k2*fac[x]%mod*fac[y]%mod*invf[x+y-i]%mod*S[x+y-i])%mod;
        k2=2ll*k2%mod;
    }
    printf("%d %d\n",ans1,ans2);
}

2021牛客4 G - product

题目要求 ∑ a i ≥ 0 , ∑ a i = D D ! ∏ i = 1 n ( a i + k ) ! \sum_{a_i\geq 0,\sum a_i=D}\frac{D!}{\prod_{i=1}^n(a_i+k)!} ai0,ai=Di=1n(ai+k)!D!。( n ≤ 50 , k ≤ 50 , D ≤ 1 0 8 n\leq 50,k\leq 50,D\leq 10^8 n50,k50,D108

我们先考虑一个更一般的式子, ∑ a i ≥ 0 , ∑ a i = D D ! ∏ i = 1 n a i ! \sum_{a_i\geq 0,\sum a_i=D}\frac{D!}{\prod_{i=1}^na_i!} ai0,ai=Di=1nai!D!

展开累乘符号, = ∑ a i ≥ 0 , ∑ a i = D D ! a 1 ! ⋅ a 2 ! ⋅ . . . ⋅ a n ! =\sum_{a_i\geq 0,\sum a_i=D}\frac{D!}{a_1!\cdot a_2!\cdot ...\cdot a_n!} =ai0,ai=Da1!a2!...an!D!

用组合数学的眼光,这可以视作将 D D D 个不同的球放入 n n n 个盒子(盒子可以为空)的方案数。

因此上式 = D n =D^n =Dn。(每个球有 n n n 个盒子可以放)

于是有结论 ∑ a i ≥ 0 , ∑ a i = D ∏ i 1 a i ! = n D D ! \sum_{a_i\geq 0,\sum a_i=D}\prod_i \frac{1}{a_i!}=\frac{n^D}{D!} ai0,ai=Diai!1=D!nD (*)

我们把原式 ∑ a i ≥ 0 , ∑ a i = D D ! ∏ i = 1 n ( a i + k ) ! \sum_{a_i\geq 0,\sum a_i=D}\frac{D!}{\prod_{i=1}^n(a_i+k)!} ai0,ai=Di=1n(ai+k)!D! 转化为 ∑ a i ≥ k , ∑ a i = D + n k ∏ i = 1 n D ! a i ! \sum_{a_i\geq k,\sum a_i=D+nk}\prod_{i=1}^n \frac{D!}{a_i!} aik,ai=D+nki=1nai!D!

= D ! ( D + n k ) ! ⋅ ∑ a i ≥ k , ∑ a i = D + n k ∏ i = 1 n ( D + n k ) ! a i ! =\frac{D!}{(D+nk)!}\cdot \sum_{a_i\geq k,\sum a_i={D+nk}}\prod_{i=1}^n \frac{(D+nk)!}{a_i!} =(D+nk)!D!aik,ai=D+nki=1nai!(D+nk)!

我们现在的问题转化为:将 D + n k D+nk D+nk 个不同的球放入 n n n 个盒子里,每个盒子至少放 k k k 个球的方案数,再乘上一个修正系数 C = D ! ( D + n k ) ! C=\frac{D!}{(D+nk)!} C=(D+nk)!D!

考虑容斥。

考虑容斥的公式:

定义 A i = { x : x 属 于 S 且 x 具 有 性 质 P i } A_i=\{x:x属于S且x具有性质P_i\} Ai={x:xSxPi}

∣ A ˉ 1 ∩ A ˉ 2 ∩ ⋯ ∩ A ˉ n ∣ = ∣ S ∣ − ∑ ∣ A i ∣ + ∑ ∣ A i ∩ A j ∣ − ∑ ∣ A i ∩ A j ∩ A k ∣ + ⋯ + ( − 1 ) n ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A n ∣ |\bar A_1\cap \bar A_2\cap \dots \cap \bar A_n|=|S|-\sum |A_i|+\sum |A_i\cap A_j|-\sum |A_i \cap A_j \cap A_k|+\dots +(-1)^n|A_1\cap A_2 \cap \dots \cap A_n| Aˉ1Aˉ2Aˉn=SAi+AiAjAiAjAk++(1)nA1A2An

那么我们的式子也形如

C ∑ i = 0 n ( − 1 ) i ∑ ∣ A j 1 ∩ A j 2 ∩ ⋯ ∩ A j i ∣ C \sum_{i=0}^n(-1)^i\sum |A_{j1}\cap A_{j2}\cap \dots \cap A_{ji}| Ci=0n(1)iAj1Aj2Aji

其中 A i A_i Ai 表示 a i < k a_i<k ai<k 的sequence的集合。

定义 d p i , j dp_{i,j} dpi,j 为把 j j j 个球分为 i i i 个非法组的方案数。

转移为:

d p i , j = ∑ s = 0 k − 1 d p i − 1 , j − s ( j s ) dp_{i,j}=\sum_{s=0}^{k-1}dp_{i-1,j-s}\binom{j}{s} dpi,j=s=0k1dpi1,js(sj)

含义为在这j个中选出s个剔除,剩下的从前一个状态中转移而来。

我们的式子由此可以写成

C ∑ i = 0 n ( − 1 ) i ( n i ) ∑ j = 0 i ( k − 1 ) d p i , j ( D + n k j ) ( n − i ) D + n k − j C\sum_{i=0}^n(-1)^i\binom{n}{i}\sum_{j=0}^{i(k-1)}dp_{i,j}\binom{D+nk}{j}(n-i)^{D+nk-j} Ci=0n(1)i(in)j=0i(k1)dpi,j(jD+nk)(ni)D+nkj

其中, ( n i ) \binom{n}{i} (in) 表示n个中选出i个非法盒子的方案数。(可以这样做的原因是,不同的Ai(例如:A_i和A_j的计算方式本质相同)等价)

∑ j = 0 i ( k − 1 ) \sum_{j=0}^{i(k-1)} j=0i(k1) 表示枚举多少个球放进这i个非法盒子(最多放i(k-1)个,因为是非法(少于k))。

( D + n k j ) \binom{D+nk}{j} (jD+nk) 表示从所有D+nk个球中选出这j个的方案数。

( n − i ) D + n k − j (n-i)^{D+nk-j} (ni)D+nkj 表示除了这j个球以外的球,他们可以在剩下的盒子中乱放。(可能乱放完了之后剩下的盒子也存在不合法的,但这无所谓,因为我们在容斥。我们只需要指定的那i个盒子非法即可)

//
// Created by Artist on 2021/8/26.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 55;
const int mod = 998244353;
int qpow(int a,int n){
    ll ans=1;
    while(n){
        if(n&1) ans=ans*a%mod;
        a=1ll*a*a%mod;
        n>>=1;
    }
    return ans;
}

int C1[maxn*maxn][maxn]; // combine
int dp[maxn][maxn*maxn]; // dp

int main(){
    int n,k,d;scanf("%d%d%d",&n,&k,&d);

    for(int i=0;i<=n*k;++i) C1[i][0]=1;
    for(int i=1;i<=n*k;++i){
        for(int j=1;j<=min(max(k,n),i);++j){
            C1[i][j]=(1ll*C1[i-1][j-1]+C1[i-1][j])%mod;
        }
    }

    dp[0][0]=1;
    for(int i=0;i<n;++i){
        for(int j=0;j<=i*(k-1);++j){
            for(int s=0;s<=k-1;++s){
                dp[i+1][j+s]=(1ll*dp[i+1][j+s]+1ll*dp[i][j]*C1[j+s][s])%mod;
            }
        }
    }

    int ans=0;
    for(int i=0;i<=n;++i){
        int sm=0;
        int fac=1,cc=(i&1?mod-C1[n][i]:C1[n][i]),x=d+n*k,y=1;
        for(int j=0;j<=i*(k-1);++j){
            sm=(1ll*sm+1ll*cc*dp[i][j]%mod*fac%mod*qpow(n-i,d+n*k-j)%mod)%mod;
            fac=1ll*fac*x%mod*qpow(y,mod-2)%mod;
            y++;x--;
        }
        ans=(ans+sm)%mod;
    }
    for(int i=d+n*k;i>d;--i) ans=1ll*ans*qpow(i,mod-2)%mod;
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值