【APIO2016】划艇

Description
  在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着个划艇学校,编号依次为到。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在Ai至Bi之间任意选择(Ai<=Bi)。值得注意的是,编号为i的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。输入所有学校的Ai、Bi的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同
Input
  第一行包括一个整数N,表示学校的数量。接下来N行,每行包括两个正整数,用来描述一所学校。其中第行包括的两个正整数分别表示Ai,Bi(1<=Ai<=Bi<=10^9),N<=500

Output
   输出一行,一个整数,表示所有可能的派出划艇的方案数除以1,000,000,007得到的余数

Solution
  开始搞了个两维dp,瞬间WA掉,没考虑到离散化以后在同一个块中的情况,然后就滚去看题解。只要再加一维???记一下现在有几个和我是同一组,然后这个用组合数乱搞一下,前缀和优化即可。
  这个方法很简单,然而我不敢定三维的状态,虽然这个的时间复杂度是三方的,但空间是两方的。尝试定义状态,然后说不定可以前缀和优化。

  我的代码所用的方法不太一样,有点类似于0/1背包的搞法,我们一段一段数字( j j )地往里边加,令g[i]表示处理到第 i i 所学校,目前的合法状态的数量是多少。
  这个需要计算calc(l,r,j),表示 [l,r) [ l , r ) 中每所学校都在 j j 区间中选数,可选可不选,第r所学校必须选,序列递增的方案数。

cal(l,r,j)=i=1m+1C(l,i)C(m,i1)=i=1m+1C(l,i)C(m,(m+1)i)=C(l+m,m+1)
 
  其中 m m 表示有多少范围中的学校可以在j区间选数。(我觉得这种写法很短很精辟啊

Source

//2018-4-20
//miaomiao
//
#include <bits/stdc++.h>
using namespace std;

#define For(i, a, b) for(int i = (a); i <= (int)(b); ++i)
#define Forr(i, a, b) for(int i = (a); i >= (int)(b); --i)

#define N (500 + 5)
#define M (1000 + 5)
const int P = 1e9 + 7;

inline int Mul(int a, int b){
    return (long long)a * b % P;
}
inline void Add(int &a, int b){
    a += b; if(a >= P) a -= P;
}

int n, m, a[N], b[N], num[M], g[N], inv[N], C[N];

int main(){
#ifndef ONLINE_JUDGE
    freopen("boat.in", "r", stdin);
    freopen("boat.out", "w", stdout);
#endif

    inv[1] = 1;
    For(i, 2, 500) inv[i] = (long long)(P - P / i) * inv[P % i] % P;

    scanf("%d", &n);
    For(i, 1, n){
        scanf("%d%d", &a[i], &b[i]);
        num[++m] = a[i], num[++m] = ++b[i];
    }

    num[++m] = 0; sort(num + 1, num + m + 1);
    m = unique(num + 1, num + m + 1) - num - 1;

    For(i, 1, n){
        a[i] = lower_bound(num + 1, num + m + 1, a[i]) - num;
        b[i] = lower_bound(num + 1, num + m + 1, b[i]) - num;
    }

    C[0] = g[0] = 1;

    For(j, 1, m - 1){
        int len = num[j + 1] - num[j];  
        For(i, 1, n) C[i] = Mul(Mul(C[i - 1], len + i - 1), inv[i]);

        Forr(i, n, 1) if(a[i] <= j && b[i] >= j + 1){

            int sum = 0, nn = 1, mc = len;
            Forr(p, i - 1, 0){
                Add(sum, Mul(mc, g[p]));
                if(a[p] <= j && b[p] >= j + 1) mc = C[++nn];
            }

            Add(g[i], sum);
        }
    }

    int ans = 0;
    For(i, 1, n) Add(ans, g[i]);
    printf("%d\n", ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值