[Codeforces961G]Partitions

本文探讨了一个关于组合数学的问题,具体是在给定物品数量和集合数量的情况下,计算所有可能的集合划分方式的权值总和。通过巧妙地利用斯特林数和组合恒等式,提出了一种高效算法来解决该问题。

题意

给出nn 个物品, 每个物品有一个权值wiwi

定义一个集合SS 的权值W(S)=|S|xSwx

定义一个划分的权值为W(R)=SRW(S)W′(R)=∑S⊆RW(S)

求将nn 个物品划分成k 个集合的所有方案的权值和

n,k2×105,wi109n,k≤2×105,wi≤109


题解

考虑把每一个元素的贡献加起来

不难发现每个元素前的系数都是一样的,,设系数是p

Ans=pi=1nwiAns=p∑i=1nwi

考虑枚举ii所在的集合的大小(S表示第二了斯特林数),),那么

p=s=1ns(n1s1)Sk1nsp=∑s=1ns(n−1s−1)Sn−sk−1

考虑到Smn=1m!i=0m(1)i(mi)(mi)nSnm=1m!∑i=0m(−1)i(mi)(m−i)n

=s=1ns(n1s1)i=0k1(1)i(k1)!(k1i)(ki1)ns=∑s=1ns(n−1s−1)∑i=0k−1(−1)i(k−1)!(k−1i)(k−i−1)n−s

=s=1ns(n1s1)i=0k1(1)ii!(ki1)ns(ki1)!=∑s=1ns(n−1s−1)∑i=0k−1(−1)ii!(k−i−1)n−s(k−i−1)!

=i=0k1(1)ii!(ki1)!s=1ns(n1s1)(ki1)ns=∑i=0k−1(−1)ii!(k−i−1)!∑s=1ns(n−1s−1)(k−i−1)n−s

考虑ns=1s(n1s1)(ki1)ns∑s=1ns(n−1s−1)(k−i−1)n−s

=s=1n(n1s1)(ki1)ns+s=1n(s1)(n1s1)(ki1)ns=∑s=1n(n−1s−1)(k−i−1)n−s+∑s=1n(s−1)(n−1s−1)(k−i−1)n−s

考虑到k(nk)=n(n1k1)k(nk)=n(n−1k−1)

=s=1n(n1s1)(ki1)ns+(n1)s=1n(n2s2)(ki1)ns=∑s=1n(n−1s−1)(k−i−1)n−s+(n−1)∑s=1n(n−2s−2)(k−i−1)n−s

考虑到i=0n(ni)(k1)ni=kn∑i=0n(ni)(k−1)n−i=kn

=(ki)n1+(n1)(ki)n2=(k−i)n−1+(n−1)(k−i)n−2

=(ki)n2(ki+n1)=(k−i)n−2(k−i+n−1)

p=i=0k1(1)i(ki)n2i!(ki1)!(ki+n1)⇒p=∑i=0k−1(−1)i(k−i)n−2i!(k−i−1)!(k−i+n−1)

O(klogk)O(klog⁡k)递推即可

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
const int N=2e5+5,P=1e9+7;
typedef int arr[N];
typedef long long ll;
int n,k,Sum;arr fac,ifac;ll p;
inline int pls(int a,int b){return a+=b,a<P?a:a-P;}
inline int fpm(int a,int b){if(b<0)return 1;int x=1;for(;b;b>>=1,a=(ll)a*a%P)if(b&1)x=(ll)x*a%P;return x;}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    sd(n),sd(k);int x;
    fp(i,1,n)sd(x),Sum=pls(Sum,x);
    fac[0]=1;fp(i,1,n)fac[i]=(ll)fac[i-1]*i%P;
    ifac[n]=fpm(fac[n],P-2);fd(i,n,0)ifac[i-1]=(ll)ifac[i]*i%P;
    fp(i,0,k-1)p+=(i&1?-1:1)*(ll)ifac[i]*ifac[k-i-1]%P*fpm(k-i,n-2)%P*(k-i+n-1)%P;
    printf("%lld\n",(ll)pls(p%P,P)*Sum%P);
return 0;
}

然后你可以发现这玩意根本推不出来好吗,于是就有另外一种比较容易的方法

注意到W(S)=|S|xSwx,W(S)=|S|∑x∈Swx,前面的系数是|S|,|S|,对于iSi∈S可以当做|S||S|中每个数都对ii产生了一次贡献

那么我们只要枚举每一个数对i的贡献即可

ii对自身的贡献显然是Snk

jij≠i,j,jii的贡献是jii在同一个集合的方案数,也就是Skn1Sn−1k

于是p=Skn+(n1)Skn1p=Snk+(n−1)Sn−1k其实也可以从上面那些东西推出来的

Smn=1m!i=0m(1)i(mi)(mi)n=i=0m(1)ii!(mi)n(mi)!,O(klogk)Snm=1m!∑i=0m(−1)i(mi)(m−i)n=∑i=0m(−1)ii!(m−i)n(m−i)!,O(klog⁡k)算就好了

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
const int N=2e5+5,P=1e9+7;
typedef int arr[N];
typedef long long ll;
int n,k,Ans;arr fac,ifac;
inline int pls(int a,int b){return a+=b,a<P?a:a-P;}
inline int fpm(int a,int b){int x=1;for(;b;b>>=1,a=(ll)a*a%P)if(b&1)x=(ll)x*a%P;return x;}
inline int S(int n,int m){
    ll tp=0;
    fp(i,0,m)tp+=(i&1?-1:1)*(ll)fpm(m-i,n)*ifac[m-i]%P*ifac[i]%P;
    return pls(tp%P,P);
}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    sd(n),sd(k);int x;
    fp(i,1,n)sd(x),Ans=pls(Ans,x);
    fac[0]=1;fp(i,1,n)fac[i]=(ll)fac[i-1]*i%P;
    ifac[n]=fpm(fac[n],P-2);fd(i,n,0)ifac[i-1]=(ll)ifac[i]*i%P;
    Ans=(ll)Ans*pls(S(n,k),(ll)(n-1)*S(n-1,k)%P)%P;
    printf("%d\n",Ans);
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值