划艇:dp/组合数/区间离散化
Description
在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着 N 个划艇学校,编号依次为 1 到 N。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。
颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为 i的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在ai到bi之间选择 至 bib_ibi 之间任意选择(ai≤bi)。
值得注意的是,编号为i 的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。
输入所有学校的 ai,bii,bi 的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同。
Input
第一行包括一个整数 N,表示学校的数量。
接下来 N 行,每行包括两个正整数,用来描述一所学校。其中第 iii 行包括的两个正整数分别表示 ai,bi(1≤ai≤bi≤1e9)i,bi(1≤ai≤bi≤109)。
Output
输出一行,一个整数,表示所有可能的派出划艇的方案数除以 1e9+79+7 得到的余数。
Sample Input
2 1 2 2 3
Sample Output
7
Hint
在只有一所学校派出划艇的情况下有 4 种方案,两所学校都派出划艇的情况下有 3 种方案,所以答案为 7。
子任务 1(9 分):1≤N≤500 且对于所有的 1≤i≤N1 \le i \le N1≤i≤N,保证 ai=bia_i=b_iai=bi。
子任务 2(22 分):1≤N≤500 且 ∑i=1-N(bi−ai)≤106i=1N(bi−ai)≤106。
子任务 3(27 分):1≤N≤100。
子任务 4(42 分):1≤N≤500。
高难标记
首先作为考试题还是需要提一下部分分的
9%算法:类似于拦截导弹,dp
31%算法:动态开点线段树,每次只枚举a和b之间的区间,维护前缀和
100%算法:
网上的题解都是坑人的!
首先看到数据范围这么大,一定需要离散化。
输入中出现的不同值最多一共有2n种,可以离散。
那么现在假如我们已经成功的离散化完毕。
原来的dp[i][j]表示最后一所派出游艇的学校是i,第i所学校派出了j个划艇
现在既然点数变少了,那么j不能再只表示j这个单点了
假如在出现的那2n个数去重离散化后第j小的数是num[j],第j+1小的数是num[j+1]
那么我们用dp[i][j]表示最后一所派出游艇的学校是i,第i所学校派出划艇的数量在[num[j],num[j+1]),注意是左闭右开那么其实左开右闭也可以。。但一定要是半开半闭的,否则就有重复区间了
那么接下来,对于每个学校,我们需要用新的这些零散的区间拼出原区间,但是因为它是左闭右开的,所以不能恰好拼出来
我们可以在输入b[i]时将其+1,并不是真的变大了,而是让离散化后的区间恰好能拼出原区间(其实就是让原区间也变成了左闭右开)
如:输入[3,11],[6,15],我们可以将其转化为[3,12),[6,16),离散化3,6,12,16四个数。
得到的区间是[3,6),[6,12),[12,16),[16,无穷大)。表示原区间:[3,12)=[3,6)+[6,12)就很简单了
接下来考虑怎么状态转移:一共两种情况
1)上一个派出游艇的学校派出的游艇数量和本学校派出的不在同一个区间
dp[i][j]+=∑ii=1->i-1∑jj=i->j-1 dp[ii][jj]
显然+=后面的那个东西是dp数值表的一个整块,我们可以处理二维前缀和,用sum表示
dp[i][j]+=sum[i-1][j-1]; sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j];
后者是一个小容斥,前两个矩形的重叠部分减去就可以了,画个图就明白了
2)上一个派出游艇的学校派出的游艇数量和本学校派出的在同一个区间
为了方便,我们枚举出现在同一区间的编号最小的学校,设之为k
那么在k到i之间的所有学校,要么也出现在这个区间,要么干脆没有派出划艇
我们求出k~i这一段的方案数,乘以k以前的方案数sum[k-1][j-1]即可
那么显然,对于k~i这一段里的学校,如果它派出的划艇数量不能在j对应的这个区间内
那么它一定不能派出游艇(在第k和第i个学校都派出了j区间内游艇的前提下)。
所以我们只需要考虑k~i之间能派出j个划艇的学校,设这样的学校数量为can(含i和k)
那么就有如下子问题:
有can个相同的区间j,从每个区间中可以选数也可以不选,要求所有选出的数严格递增,且第一个和最后一个区间必选,求方案数?
假如我们一共选择了p个(p>=2因为i和k必选),区间j中一共有len个不同的数
分两步:从len个数中选出p个从小到大排序,再从i~k中能派出划艇的can个学校中选出p-2个要派出划艇的学校、
(将选出的p个数依次赋予p个选中的学校,即那p-2个加上i和k,每个学校被赋予的数字就是它派出的数量)
对于每一种合理的分数字的方案,都是一种派划艇的方案
那么选择p个学校时的方案数是C(len,p)*C(can,p-2) = C(len,len-p)*C(can,p-2)
全部方案就是∑p=2->can C(len,len-p)*C(can,p-2)
其实p也可以从0开始枚举,因为那时C(can,p-2)=0,不会对答案产生影响
为了方便,就当作从0开始吧
可以发现两个组合数中的len-p与p-2的和是定值,考虑它的含义
你有l个物品,我有can个,你要从你的里选出len-p个,我要从我的里选出p-2个
把咱们的物品放在一起,一共can+len个,从里面随便选出len-p+p-2=len-2个不就好了么?
所以那个和式,其实就是C(can+len,len-2)=C(can+len,can+2)
所以对dp[i][j]的贡献就是∑k=1->i-1C(can+len,can+2)*sum[k-1][j-1](k可以派出j区间内的划艇数)
如果我们能O(1)计算那个组合数,那么就可以O(n3)解决了
考虑怎么计算它。我们可以发现当枚举k时,如果k不能派出j之间的划艇数,就跳过
否则,can++。反应一下,can每次只增加了1。
C(n,m)= n! / m! / (n-m)!
C(n+1,m+1)= (n+1)! /(m+1)! / (n-m)! = n! / m! / (n-m)! *(n+1)/(m+1)
所以我们只需要把逆元搞出来,组合数就可以O(1)递推了
初状态是C(len-2,0),不计入答案,随着++can,c*=(can+l-2)*inv[can]
其实也可以不必这么麻烦,组合数貌似是可以杨辉三角的。
注意一下:在枚举j的时候,因为左开右闭的性质,是从a[i]~b[i]-1而非a[i]~b[i]
提示:推荐#define int long long,既不怕取模出问题,而且能加速。
至于为什么。。。过。
还有什么问题么?评论区吧。
const int kx = ;
// Daily Orz Rank #1
// Orz Rank #1
// Rank #1
//#1
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define int long long
map<int, int> m;
int ref[], a[], b[], n, cnt, res[], dp[][], sum[][], len[], inv[];
int pow(long long a, int t = kx - , long long ans = ) {
for (; t; t >>= , (a *= a) %= kx)
if (t & )
(ans *= a) %= kx;
return ans;
}
main() {
scanf("%lld", &n);
for (int i = ; i <= n; ++i)
scanf("%lld%lld", &a[i], &b[i]), b[i]++, res[(i << ) - ] = a[i], res[i << ] = b[i];
sort(res + , res + + (n << ));
for (int i = ; i <= (n << ); ++i)
if (res[i] != res[i - ])
ref[++cnt] = res[i], m[res[i]] = cnt;
for (int i = ; i <= n; ++i) a[i] = m[a[i]], b[i] = m[b[i]];
inv[] = inv[] = ;
dp[][] = sum[][] = ;
res[cnt + ] = res[cnt] + ;
for (int i = ; i <= cnt; ++i) len[i] = ref[i + ] - ref[i];
for (int i = ; i <= ; ++i) inv[i] = (kx - 1ll * kx / i * inv[kx % i] % kx) % kx;
for (int i = ; i <= n; ++i) sum[i][] = ;
for (int i = ; i <= cnt; ++i) sum[][i] = ;
for (int i = ; i <= n; ++i) {
for (int j = ; j < a[i]; ++j)
sum[i][j] = (sum[i - ][j] + sum[i][j - ] - sum[i - ][j - ] + dp[i][j]) % kx;
for (int j = a[i]; j < b[i]; ++j) {
dp[i][j] = 1ll * sum[i - ][j - ] * len[j] % kx; // printf("%lld %lld %lld\n",i,j,dp[i][j]);
register int c = len[j] - , can = , l = len[j];
for (int k = i - ; k; --k)
if (a[k] <= j && j < b[k])
++can, (c *= (can + l - ) * inv[can] % kx) %= kx,
(dp[i][j] += 1ll * sum[k - ][j - ] * (c)) %=
kx; //, printf("%lld %lld %lld %lld %lld\n",i,j,k,(c),sum[k-1][j-1]);
sum[i][j] = ((sum[i - ][j] + sum[i][j - ]) % kx - sum[i - ][j - ] + dp[i][j] + kx) % kx;
}
for (int j = b[i]; j <= cnt; ++j)
sum[i][j] = ((sum[i - ][j] + sum[i][j - ]) % kx - sum[i - ][j - ] + dp[i][j] + kx) % kx;
}
// for(int i=1;i<=n;++i){for(int j=1;j<=cnt;++j)printf("%lld ",sum[i][j]);puts("");}puts("");
// for(int i=1;i<=n;++i){for(int j=1;j<=cnt;++j)printf("%lld ",dp[i][j]);puts("");}
printf("%lld\n", sum[n][cnt] - );
}
感谢loj码风优化
划艇:dp/组合数/区间离散化的更多相关文章
- 【区间dp+组合数+数学期望】Expression
https://www.bnuoj.com/v3/contest_show.php?cid=9148#problem/I [题意] 给定n个操作数和n-1个操作符,组成一个数学式子.每次可以选择两个相 ...
- Contest 20140708 testB dp 组合数
testB 输入文件: testB.in 输出文件testB.out 时限3000ms 问题描述: 定义这样一个序列(a1,b1),(a2,b2),…,(ak,bk)如果这个序列是方序列的话必须满足 ...
- POJ 2528 Mayor's posters 【区间离散化+线段树区间更新&&查询变形】
任意门:http://poj.org/problem?id=2528 Mayor's posters Time Limit: 1000MS Memory Limit: 65536K Total S ...
- noj 2033 一页书的书 [ dp + 组合数 ]
传送门 一页书的书 时间限制(普通/Java) : 1000 MS/ 3000 MS 运行内存限制 : 65536 KByte总提交 : 53 测试通过 : 1 ...
- 线段树区间离散化维护按秩合并并查集(可撤销)——牛客多校第八场E
模板题..去网上学了可撤销的并查集.. /* 给定一个无向图,边的属性为(u,v,l,r),表示<u,v>可以通过的size为[l,r] 求出有多少不同的size可以从1->n 把每 ...
- 线段树区间离散化——牛客多校E
这个区间离散化把我调死了.. 总之用vector来离散化,然后叶子节点维护的是一段区间,记录下每个叶子结点的起点+长度 千万要注意下标不能弄错! #include<bits/stdc++.h&g ...
- NOI2016区间bzoj4653(线段树,尺取法,区间离散化)
题目描述 在数轴上有 \(N\) 个闭区间 \([l_1,r_1],[l_2,r_2],...,[l_n,r_n]\) .现在要从中选出 \(M\) 个区间,使得这 \(M\) 个区间共同包含至少一个 ...
- HDU 5396 Expression(DP+组合数)(详解)
题目大意: 给你一个n然后是n个数. 然后是n-1个操作符,操作符是插入在两个数字之间的. 由于你不同的运算顺序,会产生不同的结果. 比如: 1 + 1 * 2 有两种 (1+1)*2 或者 ...
- BZOJ 1852 [MexicoOI06]最长不下降序列(贪心+DP+线段树+离散化)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1852 [题目大意] 给你N对数A1,B1……An,Bn.要求你从中找出最多的对, 把它 ...
随机推荐
- 并发新构件之CountDownLatch
CountDownLatch译为倒计时锁存器:JDK描述 :允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助. A CountDownLatch用给定的计数初始化. awai ...
- 一个神秘现象引发对beego框架的思考
小强最近在项目中遇到了一个很奇怪的问题:在整改日志规范时,为了避免影响现有的代码结构以及改动尽可能小的前提下,在调用记日志的SDK处将某一个字段值首字母改为大写,代码示例如下: fmt.Println ...
- 数据存储检索之B+树和LSM-Tree
作为一名应用系统开发人员,为什么要关注数据内部的存储和检索呢?首先,你不太可能从头开始实现一套自己的存储引擎,往往需要从众多现有的存储引擎中选择一个适合自己应用的存储引擎.因此,为了针对你特定的工作负 ...
- 微信小程序canvas生成并保存图片
---恢复内容开始--- 微信小程序canvas生成并保存图片,具体实现效果如下图 实现效果需要做以下几步工作 一.先获取用户屏幕大小,然后才能根据屏幕大小来定义canvas的大小 二.获取图 ...
- win10-搭建git工具
.下载安装 git .生成 SSH 密钥 ssh-keygen -t rsa -C "email@com" -b 4096 .配置gitlab 增加 SSH 密钥. .配置 git ...
- gperftools::TCMalloc
VS2013编译gperftools-2.4 1)https://github.com/gperftools/gperftools 下载 gperftools-2.4.zip 版本.2)解压 ...
- std::lock_guard 与 std::unique_lock
std::lock_guard 与 std::unique_lock 对 mutex 进行自动加解锁. mutex m; void fun() { unique_lock<mutex> m ...
- Java8新特性 - Stream API
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API对集合进行操作,就类似与使用SQL执行的数据库 ...
- jquery a标签的锚点点击的时候页面上缓慢滚动
a标签增加一个名字触发效果: $(".transition").click(function(){ if (location.pathname.replace(/^\//, '') ...
- 为什么要用dubbo,dubbo是什么,为什么要和zk结合使用?
目录 为什么要用dubbo dubbo是什么 dubbo架构 dubbo和zk关系 为什么要用dubbo? 随着互联网的发展,网站的应用规模不断扩大,常规的垂直架构已经无法应,分布式服务架构势在必行, ...