#NOIP模拟赛bzoj3449#大佬(期望好题)

#NOIP模拟赛bzoj3449#大佬(期望好题)_第1张图片

#NOIP模拟赛bzoj3449#大佬(期望好题)_第2张图片

#NOIP模拟赛bzoj3449#大佬(期望好题)_第3张图片

其实我根本就不懂得期望是什么,准确地说应该是不太熟悉,但是现在大致也知道是怎么算的了。


说说这道题,出题人的正解很成功被我们某些大佬给卡掉了,原因是还有更好的解法,可以把时间复杂度降得更低,现在来看真正的正解。


假设现在我们有一种完整的N道题难度的排列,设现在这种排列为i,然后有w[i][j]来表示这种排列下,第j - k + 1天到第j天的劳累值。

对于方案i,完成总书的劳累值就是Σw[i][j](k <= j <= n)

然后一共有M^N种排列, 即i∈[1, M^N]

那么列式如下:


上面的竖着一列求和就是在排列i下的总劳累值。

我们考虑每一行,在每种情况下,位置k的劳累值与前后的并不相关,只与它内部自己的值有关,也就是说在k这个位置上所有方案的劳累值总和能直接算出来

然后对于每一个区间大小为K的位置(即每一行求和),它们自己的劳累值总和其实是一样的。

只需要算出一行,然后乘N- K+ 1就可以了。

一行怎么算?

枚举最大劳累值j,于是这个长度为K的区间里要填至少一个j,其他的都要小于j,

于是此时有:(j ^ K - (j - 1) ^ K) * Wt[j]就是当前j的贡献,而这种贡献将出现M ^ (N - K)次(其他地方任意排列)。


对于总共的期望,一共有M ^ N种情况,而每种情况的贡献已经累和,可以选择最后来除,也可以算出每一种最大值就马上除(约分一下就是除M ^ K, 即乘逆元)


Code:

#include
#include
#include
#include
#include
using namespace std;

typedef long long LL;

const int Max = 400;
const int MOD = 1e9 + 7;

int N, M, K;
int Wt[Max + 5];

void getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9') if(c == '-')    flg = -1;
    while(c >= '0' && c <= '9'){    num = num * 10 + c - 48;    c= getchar(); }
    num *= flg;
}

int qmul(int p, int k){
    int rt = 1;
    while(k){
        if(k & 1)   rt = 1ll * rt * p % MOD;
        p = 1ll * p * p % MOD;
        k >>= 1;
    }
    return rt;
}

int main(){
    //freopen("kat.in", "r", stdin);
    //freopen("kat.out", "w", stdout);
    getint(N), getint(M), getint(K);
    //if(N < K){puts("0");    return 0;}
    for(int i = 1; i <= M; ++ i)    getint(Wt[i]);
    int Ans = 0ll;
    int deno = qmul(M, K);
    deno = qmul(deno, MOD - 2);
    for(int i = 1; i <= M; ++ i)
        Ans = (Ans + 1ll*Wt[i]*(qmul(i, K)-qmul(i - 1, K))%MOD*deno%MOD)%MOD;
    Ans = 1ll*Ans * (N - K + 1) % MOD;
    printf("%d\n", Ans);
    return 0;
}
/*
2 2 2
1 2

*/


你可能感兴趣的:(巧解,math,组合数学,NOIP,bzoj)