当前位置: 首页 > news >正文

前缀和优化DP——划艇

前缀和优化DP

例题一

划艇

NNN 所划艇学校编号为 1∼N1\sim N1N,每个学校有若干艘划艇,相同学校的划艇颜色相同,不相同学校的划艇颜色不同,第 iii 所学校可以派出 ai∼bia_i\sim b_iaibi 艘划艇参加庆典,也可以不派出划艇参加庆典。

如果第 iii 所学校会派出划艇,那么必须保证其派出的划艇大于编号小于它的学校所派出的划艇数量。

求有多少种合法派出方案?两种派出方案不同当且仅当两种方案存在一种颜色的划艇数量不同。

数据范围:1≤N≤5001\le N\le5001N5001≤ai,bi≤1091\le a_i,b_i\le 10^91ai,bi109

题解

朴素思维

可以发现所有的方案都可以 按最后一所学校派出的划艇数量 来划分,而当最后一所学校派出的划艇数量确定时,我们又可以按倒数第二所学校派出的划艇数量来划分这种方案,以此类推。

dp[i][j]dp[i][j]dp[i][j] 表示 当前 iii 所学校派出的划艇合法,且第 iii 所学校派出划艇数量为 jjj,且 jjj 大于前 i−1i-1i1 所学校派出划艇数的最大值 的方案总数。设 SiS_iSi 表示第 iii 所学校能派出的划艇 值域集合

之所以要规定 jjj 是前缀学校派出的划艇的最大值,是因为,不规定的话 dp[i][0]dp[i][0]dp[i][0] 就不好被转移,因为这个状态包含的所有方案中前缀最大的划艇数是未知的。

对于第一所学校,
dp[1][j]={1j=01j∈[a1,b1]0j∉S1dp[1][j]= \begin{cases} 1\quad j=0\\ 1\quad j\in [a_1,b_1]\\ 0\quad j\notin S_1 \end{cases} dp[1][j]=1j=01j[a1,b1]0j/S1
第一所学校如果不派划艇,对应方案数是 111

第一所学校如果派划艇,且派出的划艇在 [a1,b1][a_1,b_1][a1,b1] 之间,对应方案数是 111

第一所学校如果派划艇,且派出的划艇不在 [a1,b1][a_1,b_1][a1,b1] 之间,对应方案数是 000

对于第二所学校,
dp[2][j]={1j=0∑k=0j−1dp[1][k]j∈[a2,b2]0j∉S2dp[2][j]= \begin{cases} 1\quad j=0\\ \sum_{k=0}^{j-1}dp[1][k]\quad j\in [a_2,b_2]\\ 0\quad j\notin S_2 \end{cases} dp[2][j]=1j=0k=0j1dp[1][k]j[a2,b2]0j/S2
第二所学校如果不派划艇,那么第一所学校必然也不能派划艇,因为第二维是前缀学校派出的划艇数量的最大值。

第二所学校如果派出划艇,且派出的划艇在规定范围内,那么我们就从第一所学校里的小于第二所学校派出的划艇数量的所有状态中转移。

如果派出划艇,但不在规定范围内,那么对应方案数量是 000

对于第三所学校,
dp[3][j]={1j=0∑k=0j−1dp[1][k]+∑k=0j−1dp[2][k]j∈[a3,b3]0j∉S3dp[3][j]= \begin{cases} 1\quad j=0\\ \sum_{k=0}^{j-1}dp[1][k]+\sum_{k=0}^{j-1}dp[2][k]\quad j\in [a_3,b_3]\\ 0\quad j\notin S_3\\ \end{cases} dp[3][j]=1j=0k=0j1dp[1][k]+k=0j1dp[2][k]j[a3,b3]0j/S3
若第三所学校不派出划艇,对应方案是 111,即只能三所学校都不派出划艇。

若第三所学校派出划艇,且派出的划艇在 [ai,bi][a_i,b_i][ai,bi] 之间,设第三所学校派出的划艇数量为 jjj 则考虑如何转移:

  • 第二所学校派出划艇,那么我们从第二所学校派出的划艇数量小于 jjj 的所有状态中转移即可。
  • 若第二所学校不派出潜艇,那么我们从第一所学校中派出划艇数量小于 jjj 的所有状态中转移即可。

扩展到对于第 iii 所学校,则可以枚举每个后缀的所有学校全都不派出划艇,总方案数等价于
dp[i][j]={1j=0∑k=0j−1dp[1][k]+∑k=0j−1dp[2][k]+⋯+∑k=0j−1dp[i−1][k]j∈[ai,bi]0j∉Sidp[i][j]= \begin{cases} 1\quad j=0\\ \sum_{k=0}^{j-1}dp[1][k]+\sum_{k=0}^{j-1}dp[2][k]+\cdots+\sum_{k=0}^{j-1}dp[i-1][k]\quad j\in [a_i,b_i]\\ 0\quad j\notin S_i\\ \end{cases} dp[i][j]=1j=0k=0j1dp[1][k]+k=0j1dp[2][k]++k=0j1dp[i1][k]j[ai,bi]0j/Si
故,对于任意的 iii 转移方程为
dp[i][j]={1j=0∑k=1i−1∑t=0j−1dp[k][t]j∈[ai,bi]0j∉Sidp[i][j]= \begin{cases} 1\quad j=0\\ \sum_{k=1}^{i-1}\sum_{t=0}^{j-1}dp[k][t] \quad j\in [a_i,b_i]\\ 0\quad j\notin S_i\\ \end{cases} dp[i][j]=1j=0k=1i1t=0j1dp[k][t]j[ai,bi]0j/Si
如果把每个 dp[k][t]dp[k][t]dp[k][t] 看成孤立的点的话,∑k=1i−1∑t=0j−1dp[k][t]\sum_{k=1}^{i-1}\sum_{t=0}^{j-1}dp[k][t]k=1i1t=0j1dp[k][t] 就是以 (0,0)(0,0)(0,0) 为左上角,(i−1,j−1)(i-1,j-1)(i1,j1) 为右下角的所有点的和,即 二维前缀和

我们只需要每次算完 dp[i]dp[i]dp[i] 的所有 jjj 的时候,按顺序累加为前缀和即可。

优化

但是现在问题在于 jjj 规模是 10910^9109,内存和时间都不支持我们进行转移。

所以需要进行状态压缩,用一种方法将整个值域划分为若干区间,且保证区间个数很小,且能符合状态转移方程的逻辑。

具体如何划分区间,由于值域为 10910^9109,如果采用分块,数量级是 104.510^{4.5}104.5,数量级过于大了。

还有一种更节省空间的状态压缩,即我们涉及到的区间必然是题目中给点的 [ai,bi][a_i,b_i][ai,bi],考虑对每个学校所涉及的派出划艇数量的区间端点进行 离散化,此时整个值域 [0,1e9][0,1e9][0,1e9] 至多被划分为了 2n+12n+12n+1 个小区间,其中 nnn 至多为 500500500

那么 dpdpdp 状态就更新为 dp[i][j]dp[i][j]dp[i][j] 表示前 iii 学校已经派完了划艇,且第 iii 所学校派出在第 jjj 个区间的方案数。

对于 dp[i][j]dp[i][j]dp[i][j] 的转移,我们考虑将前 iii 所学校划分为两个部分,第一个部分是派出的划艇 不在第 jjj 区间,第二个部分是派出的划艇在 在第 jjj 个区间

对于派出的潜艇数在前不在第 jjj 个区间的方案,需要枚举前缀哪些学校派出的划艇不在第 jjj 区间,设 preprepre 表示前缀 1∼pre1\sim pre1pre 所学校派出的划艇数量不在第 jjj 区间。

那么 [pre+1,i][pre+1,i][pre+1,i] 内的学校派出的划艇数量就 必须在第 jjj 区间或干脆不派出,为什么可以不派出呢?因为题目规定不派出的情况并不会影响合法性。

为了保证枚举不重不漏规定 pre+1pre+1pre+1 学校必须在第 jjj 区间,且 iii 学校也必须在第 jjj 区间

我们考虑 [pre+1,i][pre+1,i][pre+1,i] 内有多少所学校派出的划艇能够处于第 jjj 区间,设这个确切的数量是 sss

有一个结论是,如果 sss 所学校的值域都能完全 包含 jjj 区间,那么可以用一个组合数来表示方案。

具体说来就是,给你 sss 个大小相同区间,区间长度是 lenlenlen,现在你需要对于每个区间都拿出一个数,且保证拿出的数不重复,可以不拿,请问有多少种拿数方案?

如果全部拿数出去的话可能会存在有数重复的情况,所以需要考虑哪些区间不拿,即可以看做拿 000,但必须保证至少有一个区间拿出来了数。

不妨设第 jjj 区间长度为 LenLenLen,极端的情况 LenLenLen 大小为 111,而 s>Lens>Lens>Len,那么就代表有一些区间必须选 000。因为这 sss 个数里必须有 222 个数是要在 jjj 区间的,不妨让前 s−2s-2s2 个区间都先拿出 000,那么此时有 Len+s−2Len+s-2Len+s2 个数,然后我们再从这 Len+s−2Len+s-2Len+s2 个数里面随便选 sss 个数,方案是 (Len+s−2s)\binom{Len+s-2}{s}(sLen+s2)

所以我们的区间划分方式必须保证所有学校的值域与所有区间的交集要么为区间本身,要么为空集。

我们将 ai,bi+1a_i,b_i+1ai,bi+1 进行离散化,离散化数组为 lshlshlsh,离散化之后第 iii 个区间的是 [lshi,lshi+1)[lsh_i,lsh_{i+1})[lshi,lshi+1)。由于我们把 bi+1b_i+1bi+1 离散化,所以每所学校的 bib_ibi 必定是某一个区间的右端点,而 aia_iai 必然是某个区间的左端点,所以 [ai,bi][a_i,b_i][ai,bi] 所包括的区间必然是整数个。

那么 dp[i][j]dp[i][j]dp[i][j] 的转移方程就是
dp[i][j]={1j=0∑p=0i−1∑k=0j−1dp[p][k](lenj+s−1s)ai≤j≤bidp[i][j]= \begin{cases} 1\qquad j=0\\ \sum_{p=0}^{i-1}\sum_{k=0}^{j-1} dp[p][k]\binom{len_j+s-1}{s}\qquad a_i\le j\le b_i \end{cases} dp[i][j]={1j=0p=0i1k=0j1dp[p][k](slenj+s1)aijbi
组合数只与 ppp 有关,所以当 ppp 确定,组合数确定。而此时 dp[p][k]dp[p][k]dp[p][k] 只表示散点,如果 dp[p][k]dp[p][k]dp[p][k] 表示二维前缀和的话,那么新的转移方程其实就是
dp[i][j]={1j=0∑p=0i−1(lenj+s−1s)dp[p][j−1]ai≤j≤bidp[i][j]= \begin{cases} 1\qquad j=0\\ \sum_{p=0}^{i-1}\binom{len_j+s-1}{s}dp[p][j-1]\qquad a_i\le j\le b_i \end{cases} dp[i][j]={1j=0p=0i1(slenj+s1)dp[p][j1]aijbi

具体流程
  1. 先将每个学校派出的划艇数区间的端点 ai,bi+1a_i,b_i+1ai,bi+1 进行离散化,此时能够值域被划分成 O(n)O(n)O(n) 个区间。

  2. 预处理出每个学校派出的划艇数能够完全覆盖的区间有哪些。我们希望区间之间不会重叠,所以取区间为左闭右开的,即离散化数组内的区间都表示为 [lshi,lshi+1)[lsh_{i},lsh_{i+1})[lshi,lshi+1)。先二分离散化数组中 aia_iai 的位置 LLL,假设下标从 111 开始,那么 aia_iai 就表示是第 LLL 个区间的开头。接着二分离散化数组中第一个大于 bib_ibi 的位置 RRR,一定会二分到 bi+1b_i+1bi+1 的位置 RRR,而 bi+1b_i+1bi+1 一定不会作为某个区间的右端点(规定区间是左闭右开)所以 bi+1b_i+1bi+1 是第 RRR 个区间的左端点,所以我们 bib_ibi 就是 R−1R-1R1 区间的结尾。所以 ai=La_i=Lai=Lbi=R−1b_i=R-1bi=R1 表示第 iii 所学校能完全覆盖的区间为 [ai,bi][a_i,b_i][ai,bi]。到这里,我们保证了 每所学校能派出的划艇数量都能覆盖完整的区间,即使有一所学校派出的划艇数量只能有一种情况,此时 ai=bia_i=b_iai=bi

  3. 如何预处理组合数,组合数是 C(len+s−2,s)=(len+s−2)!s!(len−2)!C(len+s-2,s)=\frac{(len+s-2)!}{s!(len-2)!}C(len+s2,s)=s!(len2)!(len+s2)!,因为 lenlenlen 可能会很大,所以不可能用阶乘或逆元来求解。

    所以考虑组合数递推,当 lenlenlen 被确定时

    s=1s=1s=1 时,方案数是 C(len,1)C(len,1)C(len,1)

    s=2s=2s=2 时,方案数是 C(len,2)C(len,2)C(len,2)

    s=3s=3s=3 时,方案数是 C(len+1,3)C(len+1,3)C(len+1,3)

    不难发现从 s≥2s\ge 2s2 以后,方案数都是 C(len+s−2,s)C(len+s-2,s)C(len+s2,s),所以我们特判 s=1s=1s=1 的情况。

    另外,当 s→s+1s\rarr s+1ss+1 时,组合数 C(len+s−2,s)→C(len+s−1,s+1)C(len+s-2,s)\rarr C(len+s-1,s+1)C(len+s2,s)C(len+s1,s+1)。 而 C(len+s−1,s+1)C(len+s-1,s+1)C(len+s1,s+1) 可以由 C(len+s−2,s)C(len+s-2,s)C(len+s2,s) 得到,即 C(len+s−1,s+1)=(len+s−1)!(s+1)!(len−2)!C(len+s-1,s+1)=\frac{(len+s-1)!}{(s+1)!(len-2)!}C(len+s1,s+1)=(s+1)!(len2)!(len+s1)!,而 C(len+s−2,s)=(len+s−2)!s!(len−2)!C(len+s-2,s)=\frac{(len+s-2)!}{s!(len-2)!}C(len+s2,s)=s!(len2)!(len+s2)!,不难发现 C(len+s,s+1)=C(len+s−1,s)×len+s−1s+1C(len+s,s+1)=C(len+s-1,s)\times\frac{len+s-1}{s+1}C(len+s,s+1)=C(len+s1,s)×s+1len+s1

    所以我们只需要得到 sss 的逆元即可,最多不会超过 2N2N2N,所以可以预处理逆元。

    #include <bits/stdc++.h>
    using namespace std;
    //#pragma GCC optimize(2)
    #define int long long
    #define endl '\n'
    #define PII pair<int,int>
    #define INF 1e18
    const int N = 1001;
    const int MOD = 1e9 + 7;int a[N], b[N];
    int fac[4*N], inv[4*N];int power (int a, int b) {int s = 1;while (b) {if (b & 1) s = s * a % MOD;a = a * a % MOD;b = b >> 1;}return s;
    }void prework() {inv[0] = 1;for (int i = 1; i < 3*N; i++) inv[i] = power(i, MOD - 2) % MOD;
    }void slove () {prework();int n;cin >> n;vector <int> lsh(1, 0);for (int i = 1; i <= n; i++) {cin >> a[i] >> b[i];lsh.push_back(a[i]);lsh.push_back(b[i] + 1);}sort (lsh.begin(), lsh.end());lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());int cnt = lsh.size() - 1;for (int i = 1; i <= n; i++) {a[i] = lower_bound(lsh.begin(), lsh.end(), a[i]) - lsh.begin();b[i] = upper_bound(lsh.begin(), lsh.end(), b[i]) - lsh.begin();b[i] --;}vector <int> len(cnt + 1, 0);for (int i = 1; i <= cnt - 1; i++) {len[i] = lsh[i + 1] - lsh[i];}vector <vector <int>> dp (n + 1, vector <int> (cnt, 0));for (int i = 0; i <= cnt - 1; i++) dp[0][i] = 1;for (int i = 1; i <= n; i++) {dp[i][0] = 1;for (int j = a[i]; j <= b[i]; j++) {int s = 1;vector <int> C(n + 1, 0);C[1] = len[j];C[2] = len[j] * (len[j] - 1) % MOD * inv[2] % MOD;for (int p = 3; p <= n; p++) {C[p] = C[p - 1] * (len[j] + p - 2) % MOD * inv[p] % MOD;}// s == 1 的情况dp[i][j] = dp[i - 1][j - 1] * len[j] % MOD;// len 恰好等于 1,那么枚举前缀会导致重复if (len[j] == 1) continue;for (int p = i - 1; p >= 1; p--) {if (a[p] <= j && b[p] >= j) {s ++;dp[i][j] = (dp[i][j] + dp[p - 1][j - 1] * C[s] % MOD) % MOD;}}}for (int j = 1; j <= cnt - 1; j++) {dp[i][j] = (dp[i][j] + dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]) % MOD;dp[i][j] = (dp[i][j] + MOD) % MOD;}}cout << ((dp[n][cnt - 1] - 1) % MOD + MOD) % MOD << endl;}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
    }
    
http://www.dtcms.com/a/605396.html

相关文章:

  • 珠海网站建设熊掌号建设工程是指哪些工程
  • 网站推广渠道咨询报价表
  • 【一天一个计算机知识】—— 【编程百度】翻译环境与运行环境
  • 【Redis存储】Redis介绍
  • 计算机组成原理---总线与输入/输出系统
  • Python 的几个重要的相关概念
  • 零基础学AI大模型之Milvus核心:分区-分片-段结构全解+最佳实践
  • Spring AI Alibaba 自学习AI智能体实战:让AI越用越懂你
  • Springboot主配置文件
  • 家具电商网站建设一定要建设好网站才能备案吗
  • 医药建设网站wordpress 柚子皮下载
  • Java被裁后如何快速上岸?
  • 拥抱元宇宙:通过GoogleVR来了解VR到底是什么
  • 【UE5】- VR小技巧 :用PC处理代替频繁使用VR头显开发
  • 攻击者利用自定义GPT的SSRF漏洞窃取ChatGPT机密数据
  • 支付招聘网站套餐费用怎么做帐wordpress preg_replace 关键词 alt
  • GPT-5.1发布:深入解读与 GPT-5、GPT-4o 在性能与安全基准上的全面对比
  • 两台虚拟机搭建多机区块链网络
  • Vue.js栏目 - 目录一展
  • 网站采集怎么做莱芜金点子广告电子版2022最新
  • 2025 最硬核技术创新,重构 AI 感知与决策逻辑
  • flowable05外置表单和绘制流程图
  • UDP网络编程:从客户端封装到服务端绑定的深度实践
  • Arbess从初级到进阶(4) - 使用Arbess+GitLab实现React.js 项目自动化部署
  • 内网穿透技术
  • asp.net做织梦网站长沙商城网站开发
  • [免费]基于Python的深度学习豆瓣电影数据可视化+情感分析推荐系统(Flask+Vue+LSTM+scrapy)【论文+源码+SQL脚本】
  • SQL 分类
  • 微信小程序项目上传到git仓库(完整操作)
  • Vue 3响应式系统的底层机制:Proxy如何实现依赖追踪与自动更新?