CF961G

首先我们考虑直接搞

考虑每个元素的贡献,得表达式:

$ans=\sum_{i=1}^{n}w_{i}\sum_{j=1}^{n}jC_{n-1}^{j-1}S(n-j,k-1)$

即枚举每个元素所在集合中元素个数及划分方案数

这个玩意显然是$O(n^{2})$的

有大佬把它化简之后变成了可以直接递推的东西,但是...太恶心了好吗

因此我们换个思想:

我们知道,对于一种划分方式,元素$i$的贡献是$|S|w_{i}$

那么我们可以理解为在这种划分方式下,这个集合中每个元素都产生了$w_{i}$的贡献

那么我们分成两部分来考虑这个问题

首先,一个元素本身无论在哪个集合里都会产生$w_{i}$的贡献,这个贡献是一定的,只与集合划分方法有关,因此这个元素本身对$w_{i}$的贡献是$w_{i}S(n,k)$

接下来,我们考虑其他元素对这个$w_{i}$的贡献:去掉第$i$个元素之后,其他元素仍然可以划分成$k$个集合,划分方案数为$S(n-1,k)$

在每种划分方式下,我们都可以把第$i$个元素放进任意一个集合里面去,产生的贡献等于集合大小*$w_{i}$(注意这个集合大小显然不包含$i$这个元素,因为他本身的贡献我们已经统计过了)

这样的话,对每种划分方式,对$w_{i}$产生的贡献其实都是$(n-1)$,因为每个集合大小加起来就是$(n-1)$

那么表达式就变成了$\sum_{i=1}^{n}w_{i}[S(n,k)+(n-1)S(n-1,k)]$

也就是$[S(n,k)+(n-1)S(n-1,k)]\sum_{i=1}^{n}w_{i}$

注意到第二类斯特林数可以$O(klog_{2}k)$递推,后面那个东西读入的时候累个前缀和即可

第二类斯特林数的递推式:$S(n,m)=\frac{1}{m!}\sum_{i=0}^{m}(-1)^{i}C_{m}^{i}(m-i)^{n}$

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
const ll mode=1000000007;
ll minv[200005];
ll inv[200005];
ll mul[200005];
ll n,k,s;
ll w[200005];
void init()
{
    minv[0]=minv[1]=inv[0]=inv[1]=mul[0]=mul[1]=1;
    for(int i=2;i<=200000;i++)
    {
        inv[i]=(mode-mode/i)*inv[mode%i]%mode;
        minv[i]=minv[i-1]*inv[i]%mode;
        mul[i]=mul[i-1]*i%mode;
    }
}
ll pow_mul(ll x,ll y)
{
    ll ret=1;
    while(y)
    {
        if(y&1)ret=ret*x%mode;
        x=x*x%mode,y>>=1;
    }
    return ret;
}
ll C(ll x,ll y)
{
    if(x<y)return 0;
    return mul[x]*minv[y]%mode*minv[x-y]%mode;
}
ll get_S(ll x,ll y)
{
    ll ret=0,f=1;
    for(int i=0;i<=y;i++)ret=(ret+f*C(y,i)*pow_mul(y-i,x)%mode+mode)%mode,f=-f;
    return ret*minv[y]%mode;
}
int main()
{
    init();
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&w[i]),s=(s+w[i])%mode;
    printf("%lld\n",s*((get_S(n,k)+(n-1)*get_S(n-1,k)%mode)%mode)%mode);
    return 0;
}

 

posted @ 2019-07-05 10:03  lleozhang  Views(171)  Comments(0Edit  收藏
levels of contents