zoukankan      html  css  js  c++  java
  • [APIO2016]划艇

    挺神奇的一个做法,貌似叫做值域分段?

    首先考虑一个简单的暴力做法,令 (dp_{i, j}) 为考虑完前 (i) 个学校,当前派出的最大划艇数量为 (j) 的方案。

    那么按照第 (i) 个学校派不派划艇转移就会有:

    [dp_{i, j} = dp_{i - 1, j} ]

    [dp_{i, j} = sumlimits_{k = 0} ^ {j - 1} dp_{i - 1, k} (a_i le j le b_i) ]

    前缀和优化一下就可以做到 (O(nmax(a_i, b_i))) 了,但因为值域过大,这个做法显然是不行的。

    那么一个直接的想法就是通过改写状态来解决这个问题,但是很难发现一个不依赖于值域的状态,那么就只能转化这个问题了。

    既然值域过大,平常做其他的题目常用的办法就是使用离散化,何不在本题上也尝试一番呢?

    离散化过后你会发现,值域会被至多划分成 (2n - 1) 个简单区间(不包含其他端点)。

    并且每一个简单区间和原先的每所学校的划艇限制 ([a_i, b_i]) 只能是包含或者是相离关系。

    这意味着如果一所学校选择在某个区间内的一个权值放置划艇,可行的方案数实质上已经与这个学校无关了,只于放置的这个区间大小有关了。

    那么就可以考虑一个基于区间放置的一个 (dp)

    具体地,对于学校 (i),考虑选择区间 (j) 中的一个权值放置划艇。

    因为题目的要求,那么 (i) 之前的所有学校能放的区间一定不大于 (j),这样就形成了拓扑序,可以考虑 (dp) 了。

    不难发现可以令 (dp_{i, j, k}) 表示当前考虑到第 (i) 所学校,当前第 (j) 个区间中放置了 (k) 个学校的划艇的方案。

    同样根据当前学校是否放置划艇转移即可:

    [dp_{i, j, k} = dp_{i - 1, j, k} ]

    [dp_{i, j, 1} = sumlimits_{k = 0} ^ {j - 1} sumlimits_{l = 1} ^ {min(i - 1, d_{k + 1} - d_k)}dp_{i - 1, k, l} ]

    对于这条转移,直接记录前缀和即可。

    最后,考虑如何在当前区间最后再添加一个数。

    可以发现,从一个长度为 (L) 的区间中选出 (n) 个数形成递增序列的方案就相当于选出 (n) 个互不相同的数然后按顺序排好,即:(dbinom{L}{n})

    添加一个数的方案可以考虑先把之前的方案先除以 (dbinom{L}{n - 1}) 再乘 (dbinom{L}{n}) 即可。

    添加一个数的方案即 (dfrac{inom{L}{n}}{inom{L}{n - 1}} = dfrac{L - n + 1}{n})

    因此有转移:

    [dp_{i, j, k} = dp_{i - 1, j, k - 1} imes frac{L - n + 1}{n}(k > 1) ]

    最终实现的时候简单的区间要定义为左闭右开,这样就不会出现端点处选择记重的情况了,同时,需要滚动数组。

    #include <bits/stdc++.h>
    using namespace std;
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    const int N = 500 + 5;
    const int Mod = 1e9 + 7;
    int n, tot, cur, a[N], b[N], inv[N], d[N * 2], S[2][N * 2], dp[2][N * 2][N];
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
    int Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
    int Mul(int a, int b) { return 1ll * a * b % Mod;}
    int fpow(int a, int b) { int ans = 1; for (; b; a = Mul(a, a), b >>= 1) if(b & 1) ans = Mul(ans, a); return ans;}
    int main() {
        n = read();
        rep(i, 1, n) inv[i] = fpow(i, Mod - 2);
        rep(i, 1, n) a[i] = d[++tot] = read(), b[i] = d[++tot] = read() + 1;
        sort(d + 1, d + tot + 1), tot = unique(d + 1, d + tot + 1) - d - 1;
        cur = dp[0][0][0] = 1; 
        rep(i, 0, tot - 1) S[0][i] = 1;
        rep(i, 1, n) {
            rep(j, 0, tot - 1) rep(k, 0, i) dp[cur][j][k] = 0;
            rep(j, 0, tot - 1) rep(k, 0, i) dp[cur][j][k] = Inc(dp[cur][j][k], dp[cur ^ 1][j][k]);
            rep(j, 1, tot - 1) if(a[i] <= d[j] && b[i] >= d[j + 1]) {
                dp[cur][j][1] = Inc(dp[cur][j][1], Mul(S[cur ^ 1][j - 1], d[j + 1] - d[j]));
                int L = min(i, d[j + 1] - d[j]);
                rep(k, 2, L) dp[cur][j][k] = Inc(dp[cur][j][k], Mul(dp[cur ^ 1][j][k - 1], Mul(d[j + 1] - d[j] - k + 1, inv[k])));
            }
            rep(j, 0, tot - 1) S[cur][j] = 0;
            rep(j, 0, tot - 1) {
                if(j) S[cur][j] = S[cur][j - 1];
                rep(k, 0, i) S[cur][j] = Inc(S[cur][j], dp[cur][j][k]);
            }
            cur ^= 1;
        }
        printf("%d", Dec(S[cur ^ 1][tot - 1], 1));
        return 0;
    }
    

    值得一提的是,某些能运用在其他领域的做法,可能在这个领域的某些特殊问题上也会适用,不要忽略这个方面的思考。

    GO!
  • 相关阅读:
    Android开发探秘之一:创建可以点击的Button
    TCP之心跳包实现思路
    Asp.net与Dojo交互:仪器仪表实现
    Asp.net Json数据解析的一种思路
    Asp.net通过Jquery操作WebService进行Ajax读写
    GridView自定义删除操作
    从客户端中检测到有潜在危险的request.form值
    JUC-Condition线程通信
    ModelAndView 配置与使用
    SpringMVC之ModelAndView的用法(转)
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13843687.html
Copyright © 2011-2022 走看看