【ybt金牌导航8-3-6】【luogu P3270】成绩比较(DP)(拉格朗日插值)

成绩比较

题目链接:ybt金牌导航8-3-6 / luogu P3270

题目大意

有 n 个人,第一个是自己,然后每个人每科有分数,给你科目数量,科目最高分,你的排名(排名比你高的分数都比你大,排名比你低的分数都比你小)。
然后告诉了你你完胜了多少个人(即每一科都大于等于它),然后问你有多少种满足条件的分数情况。
只要有一个人有一科的分数不同,那这个分数情况就是不同的。

思路

我们考虑 DP,设 fi,j 为当前看了前 i 科,暂时完胜 j 个人。
显然初始化 f0,n1=1

然后考虑转移:(先把式子给出来)
fi,j=l=jkfi1,l(jl)(nrijn1l)p=1uipnri(uip)ri1

我们可以把这个转移分成三部分分别解释:
fi1,l:这个显然。

(jl)(nrijn1l):我们考虑从原本 l 个完胜的选出 j 个继续完胜,那就是 (jl),然后你胜的次数就用了 j,一共能胜的次数是 nri(败的次数是 ri1),还有 nrij 次,随机分配给剩下的。
然后要注意的是部分分派给 l 个中没有被选到的,所以是 (nrijn1l)

p=1uipnri(uip)ri1:这个就是你要给所有人分配分数(你前面只是分配了排名)
然后你就考虑枚举自己的分数,那我们已经知道它胜了 nri 个人,败了 ri1 个人。
那其他人之间的胜负我们不用管,所以被胜的人可以选 1p(记得可以相同),一共是 p 种,所以是 pnri。被败的人可以选 p+1ui,一共是 uip,所以是 (uip)ri1

然后我们发现 ui 很大。
然后我们考虑优化这一维,观察发现 nriri1 在枚举 i 的时候已经固定。
那就相当于 i=1yxk(yx)l 这种形式。

然后你会发现这两个分别是 k,l 项的多项式,然后乘在一起就是 k+l 项,在原式中 k=nri,l=ri1,所以 k+l=n1,那前缀和一下就是 n 项的多项式!
那多项式我们就可以直接用拉格朗日插值来求 ui 时的值了。
(我写题的时候没细看用了 n+1 项的,不过都无所谓可以过)

然后就搞好了。

代码

#include<cstdio> #define ll long long #define mo 1000000007 using namespace std; ll n, m, k, u[109], r[109], inv_mo1; ll jc[109], inv[109], f[109][109]; ll y[109], pre[109], suf[109], lft; ll ksm(ll x, ll y) { x = (x % mo + mo) % mo; ll re = 1; while (y) { if (y & 1) re = re * x % mo; x = x * x % mo; y >>= 1; } return re; } ll C(ll n, ll m) { if (n < m) return 0; if (m < 0) return 0; return jc[n] * inv[m] % mo * inv[n - m] % mo; } ll query(ll k, ll n) {//拉格朗日插值 ll re = 0; for (int i = 1; i <= k; i++) { re = (re + y[i] * pre[i - 1] % mo * suf[i + 1] % mo * inv[i - 1] % mo * inv[k - i] % mo * (((k - i) & 1) ? inv_mo1 : 1) % mo) % mo; } return re; } int main() { inv_mo1 = ksm(mo - 1, mo - 2);//预处理拉格朗日插值的逆元,这样就不用带log(组合数中也用到) jc[0] = 1; for (int i = 1; i <= 108; i++) jc[i] = jc[i - 1] * i % mo; inv[0] = inv[1] = 1; for (int i = 2; i <= 108; i++) inv[i] = inv[mo % i] * (mo - mo / i) % mo; for (int i = 1; i <= 108; i++) inv[i] = inv[i - 1] * inv[i] % mo; scanf("%lld %lld %lld", &n, &m, &k); for (int i = 1; i <= m; i++) scanf("%lld", &u[i]); for (int i = 1; i <= m; i++) scanf("%lld", &r[i]); f[0][n - 1] = 1;//当前 i 科,j 个碾压 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n + 2; j++)//提前把拉格朗日插值要的东西能先求就先求,减小复杂度 y[j] = (y[j - 1] + ksm(j, n - r[i]) * ksm(u[i] - j, r[i] - 1) % mo) % mo; pre[0] = 1; for (int j = 1; j <= n + 2; j++) pre[j] = pre[j - 1] * (u[i] - j + mo) % mo; suf[n + 2 + 1] = 1; for (int j = n + 2; j >= 1; j--) suf[j] = suf[j + 1] * (u[i] - j + mo) % mo; for (int j = k; j < n; j++) { for (int l = j; l < n; l++) {//DP lft = C(l, j) * C(n - 1 - l, n - r[i] - j) % mo; f[i][j] = (f[i][j] + f[i - 1][l] * lft % mo * query(n + 2, u[i]) % mo) % mo; } } } printf("%lld", f[m][k]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-3-6.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(36)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示