其实我根本就不懂得期望是什么,准确地说应该是不太熟悉,但是现在大致也知道是怎么算的了。
说说这道题,出题人的正解很成功被我们某些大佬给卡掉了,原因是还有更好的解法,可以把时间复杂度降得更低,现在来看真正的正解。
假设现在我们有一种完整的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<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
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
*/