zoukankan      html  css  js  c++  java
  • 划艇:dp/组合数/区间离散化

    Description

    在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着 N 个划艇学校,编号依次为 1N。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。

    颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为 i的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在ai到bi之间选择​​ 至 bib_ibi​​ 之间任意选择(aibi)。

    值得注意的是,编号为i 的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。

    输入所有学校的 ai,bii​​,bi​​ 的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同。

    Input

    第一行包括一个整数 N,表示学校的数量。
    接下来 N 行,每行包括两个正整数,用来描述一所学校。其中第 iii 行包括的两个正整数分别表示 ai,bi(1≤ai≤bi≤1e9)i​​,bi​​(1ai​​bi​​109​​)。

    Output

    输出一行,一个整数,表示所有可能的派出划艇的方案数除以 1e9+79​​+7 得到的余数。

    Sample Input

    2 1 2 2 3

    Sample Output

    7

    Hint

    在只有一所学校派出划艇的情况下有 4 种方案,两所学校都派出划艇的情况下有 3 种方案,所以答案为 7

    子任务 1(9 分):1N500 且对于所有的 1≤i≤N1 le i le N1iN,保证 ai=bia_i=b_iai​​=bi​​。

    子任务 2(22 分):1N500 且 i=1-N(bi−ai)≤106i=1N​​(bi​​ai​​)106​​。

    子任务 3(27 分):1N100。

    子任务 4(42 分):1N500。

    高难标记

    首先作为考试题还是需要提一下部分分的

    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-1jj=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,既不怕取模出问题,而且能加速。

    至于为什么。。。过。

    还有什么问题么?评论区吧。

     1 const int kx = 1000000007;
     2 // Daily Orz Rank #1
     3 // Orz Rank #1
     4 // Rank #1
     5 //#1
     6 #include <cstdio>
     7 #include <map>
     8 #include <algorithm>
     9 using namespace std;
    10 #define int long long
    11 map<int, int> m;
    12 int ref[1002], a[502], b[502], n, cnt, res[1002], dp[502][1002], sum[502][1002], len[1002], inv[502];
    13 int pow(long long a, int t = kx - 2, long long ans = 1) {
    14     for (; t; t >>= 1, (a *= a) %= kx)
    15         if (t & 1)
    16             (ans *= a) %= kx;
    17     return ans;
    18 }
    19 main() {
    20     scanf("%lld", &n);
    21     for (int i = 1; i <= n; ++i)
    22         scanf("%lld%lld", &a[i], &b[i]), b[i]++, res[(i << 1) - 1] = a[i], res[i << 1] = b[i];
    23     sort(res + 1, res + 1 + (n << 1));
    24     for (int i = 1; i <= (n << 1); ++i)
    25         if (res[i] != res[i - 1])
    26             ref[++cnt] = res[i], m[res[i]] = cnt;
    27     for (int i = 1; i <= n; ++i) a[i] = m[a[i]], b[i] = m[b[i]];
    28     inv[0] = inv[1] = 1;
    29     dp[0][0] = sum[0][0] = 1;
    30     res[cnt + 1] = res[cnt] + 1;
    31     for (int i = 1; i <= cnt; ++i) len[i] = ref[i + 1] - ref[i];
    32     for (int i = 2; i <= 500; ++i) inv[i] = (kx - 1ll * kx / i * inv[kx % i] % kx) % kx;
    33     for (int i = 1; i <= n; ++i) sum[i][0] = 1;
    34     for (int i = 1; i <= cnt; ++i) sum[0][i] = 1;
    35     for (int i = 1; i <= n; ++i) {
    36         for (int j = 1; j < a[i]; ++j)
    37             sum[i][j] = (sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + dp[i][j]) % kx;
    38         for (int j = a[i]; j < b[i]; ++j) {
    39             dp[i][j] = 1ll * sum[i - 1][j - 1] * len[j] % kx;  // printf("%lld %lld %lld
    ",i,j,dp[i][j]);
    40             register int c = len[j] - 1, can = 1, l = len[j];
    41             for (int k = i - 1; k; --k)
    42                 if (a[k] <= j && j < b[k])
    43                     ++can, (c *= (can + l - 2) * inv[can] % kx) %= kx,
    44                         (dp[i][j] += 1ll * sum[k - 1][j - 1] * (c)) %=
    45                         kx;  //, printf("%lld %lld %lld %lld %lld
    ",i,j,k,(c),sum[k-1][j-1]);
    46             sum[i][j] = ((sum[i - 1][j] + sum[i][j - 1]) % kx - sum[i - 1][j - 1] + dp[i][j] + kx) % kx;
    47         }
    48         for (int j = b[i]; j <= cnt; ++j)
    49             sum[i][j] = ((sum[i - 1][j] + sum[i][j - 1]) % kx - sum[i - 1][j - 1] + dp[i][j] + kx) % kx;
    50     }
    51     // for(int i=1;i<=n;++i){for(int j=1;j<=cnt;++j)printf("%lld ",sum[i][j]);puts("");}puts("");
    52     // for(int i=1;i<=n;++i){for(int j=1;j<=cnt;++j)printf("%lld ",dp[i][j]);puts("");}
    53     printf("%lld
    ", sum[n][cnt] - 1);
    54 }
    感谢loj码风优化
  • 相关阅读:
    sqlserver游标使用误区
    工作笔记——sqlserver引号的运用
    疯狂JAVA——数组
    工厂模式、单例和多例
    数据库数据交互详解(一)
    2016-4-6
    2016-4-5 博问问题、答题和查看收获
    Maven+Spring Batch+Apache Commons VF学习
    你忽视的静态类的作用(必看)
    Wireshark抓包工具使用教程以及常用抓包规则
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11158757.html
Copyright © 2011-2022 走看看