P3643 [APIO2016] DP

158 篇文章 1 订阅
题意

传送门 P3643 [APIO2016] 划艇

题解

令值域规模为 M M M。朴素的 DP 是 O ( ( N M ) 2 ) O\Big((NM)^2\Big) O((NM)2),前缀和优化后可以做到 O ( N 2 M ) O(N^2M) O(N2M)。由于 M M M 规模为 1 0 9 10^9 109,考虑离散化;为了方便处理,将区间表示为左闭右开。

f i , j f_{i,j} fi,j 为学校 i i i 派出划艇的数量在离散值 [ j , j + 1 ) [j,j+1) [j,j+1) 代表的区间中,且学校 i i i 是派出划艇的学校中编号最大的学校,上述约束下的方案数。

对于 j ′ < j j^{\prime}<j j<j 的情况可以直接递推统计贡献,但 j ′ = j j^{\prime} = j j=j 则难以处理。考虑枚举编号最大且满足 j ′ < j j^{\prime} < j j<j 的学校 i ′ i^{\prime} i,学校 [ 0 , i ′ ] [0,i^{\prime}] [0,i] 的贡献是 f i ′ , j ′ f_{i^{\prime},j^{\prime}} fi,j,为处理边界条件,令 f − 1 , 0 = 1 f_{-1,0} = 1 f1,0=1;学校 ( i ′ , i ] (i^{\prime}, i] (i,i] 的贡献为 ( d + n − 1 n ) d+n-1\choose n (nd+n1),其中 d d d 为区间 [ j , j + 1 ) [j,j+1) [j,j+1) 的规模, n n n ( i ′ , i ] (i^{\prime}, i] (i,i] 中满足可以派遣的划艇数量在区间 [ j , j + 1 ) [j,j+1) [j,j+1) 的学校数量。

对后者进行证明。令不派出划艇的学校,所派出的数量为 − 1 -1 1;其余为 [ 0 , M ) [0,M) [0,M) 单调递增的值。那么规模为 n n n 的学校派出划艇的方案,等价于从 n − 1 n-1 n1 − 1 -1 1 [ 0 , M ) [0,M) [0,M) n n n 个数的组合数;因为若第 i i i − 1 -1 1 被选择,则学校 i i i 不派出,未选择 − 1 -1 1 的学校数量与选择的非 − 1 -1 1 数字依序一一对应。

递推式为 f i , j = ∑ i ′ < i ( d + n − 1 n ) ∑ j ′ < j f i ′ , j ′ f_{i,j} = \sum\limits_{i^{\prime}<i} {d+n-1 \choose n}\sum\limits_{j^{\prime}<j} f_{i^{\prime},j^{\prime}} fi,j=i<i(nd+n1)j<jfi,j 前缀和优化,总时间复杂度 O ( N 3 ) O(N^3) O(N3)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MOD = 1E9 + 7;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0);
    int N;
    cin >> N;
    vector<int> A(N), B(N);
    for (int i = 0; i < N; ++i)
        cin >> A[i] >> B[i];
    vector<int> xs(1, 0);
    for (int i = 0; i < N; ++i)
        xs.push_back(A[i]), xs.push_back(++B[i]);
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(), xs.end()), xs.end());
    for (int i = 0; i < N; ++i)
    {
        A[i] = lower_bound(xs.begin(), xs.end(), A[i]) - xs.begin();
        B[i] = lower_bound(xs.begin(), xs.end(), B[i]) - xs.begin();
    }
    vector<ll> inv(N + 1);
    inv[1] = 1;
    for (int i = 2; i <= N; ++i)
        inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
    int M = xs.size();
    vector<vector<ll>> dp(N + 1, vector<ll>(M));
    vector<vector<ll>> sum(N + 1, vector<ll>(M));
    dp[0][0] = 1;
    for (int j = 0; j < M; ++j)
        sum[0][j] = 1;
    for (int i = 0; i < N; ++i)
        for (int j = 1; j + 1 < M; ++j)
        {
            if (A[i] <= j && j + 1 <= B[i])
            {
                ll n = 1;
                ll d = xs[j + 1] - xs[j];
                ll c = d;
                ll res = 0;
                for (int k = i - 1; k >= -1; --k)
                {
                    (res += c * sum[k + 1][j - 1] % MOD) %= MOD;
                    if (A[k] <= j && j + 1 <= B[k])
                        ++n, c = c * (d + n - 1) % MOD * inv[n] % MOD;
                }
                dp[i + 1][j] = res;
            }
            sum[i + 1][j] = (sum[i + 1][j - 1] + dp[i + 1][j]) % MOD;
        }
    ll res = 0;
    for (int i = 0; i < N; ++i)
        (res += sum[i + 1][M - 2]) %= MOD;
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值