• [NOI2019] 机器人


    听说和今年联合省选 \(T2\) 蛮像。

    前置题目:
    [APIO2016]划艇

    先考虑暴力 \(dp\)
    \(f_{i,j}\) 为前 \(i\) 所学校中,第 \(i\) 所学校参赛,并派出 \(j\) 艘划艇的方案数,这样答案即 \(\sum\sum f_{i,j}\)
    转移为:
    \(f_{0,1} = 1\)
    \(f_{i,j} = \sum_{k = 1}^{j - 1}\sum_{p = 0}^{i - 1}f_{p,k}\)
    考虑第二维大小很大,我们对其离散化:
    这样变成了 \(O(n)\) 个区间,我们考虑对这些区间操作:
    \(f_{i,j}\) 表示最后选取的学校的个数所落在\(j\)区间的个数
    那么有:\(f_{i,j} = \sum_{k = 1}^{j - 1}\sum_{p = 0}^{i - 1} \binom{L + m - 1}{m} f_{p,k}\)
    最后对后者中的前缀求和即可。

    那么我们对应来这个题:
    考虑如果我们使用区间\(dp\),每次选取最右边的最大值,则其成功将其划分了子问题,因为左侧无法通过来到右侧,右侧也无法来到左侧。

    而且考虑到我们这个最大值,显然其可以做到左右两边,所以其在区间中可以选的位置 \(O(1)\)

    打个表发现实际上只有 \(2000\) 个左右的区间会被用到。

    \(f_{[l,r],j}\)\([l,r]\) 上,最大值为 \(j\) 的方案数。

    那么我们可以暴力 \(dp\) 一下,大概是 \(O(n^2m)\).

    点击查看代码
    inline void solve(int l,int r){
    	if(vis[l][r])return;
    	int now = ++ cnt;vis[l][r] = now;
    	if(l > r)dp[now][0] = 1;
    	else{
    		for(int i = l;i <= r;++i){
    			if(abs(i - l - r + i) <= 2){
    				solve(l,i - 1),solve(i + 1,r);
    				for(int j = a[i];j <= b[i];++j)
    				dp[now][j] = (dp[now][j] + dp[vis[l][i - 1]][j] * dp[vis[i + 1][r]][j - 1] % mod) % mod;
    			}
    		}
    	}
    	for(int i = 1;i <= m;++i)
    	dp[now][i] = (dp[now][i] + dp[now][i - 1]) % mod; 
    }
    
    

    接下来,我们大胆猜想:
    在一个固定值域上 \([x,y]\)
    \(f_{[l,r],..}\) 是一个关于 \(k\)\(r - l + 1\) 项多项式。

    证明过程略去,可以参照其他博客。

    那么考虑因为每个点的值域不同,我们采用前置题目的做法:

    将其分为 \(O(n)\)\([c_t,c_{t+1})\),对每一段都操作

    具体操作是:对每一段都暴力\(dp\)求出前 \(\min(n,len)\) 个点值,然后插值出 \(f_{[l,r],c_{t + 1} - 1}\),作为操作下一段的常数操作,感觉看代码可能更清晰一些。

    前置知识:下列代码用到了给定 \(g_{1,...k}\) 点值时的线性插值:

    \(g_n = \sum_{i = 1}^k (-1) ^ {k - i} y_i \frac{pre_{i - 1}\ \ \ suf_{i + 1}}{(i-1)!(k - i)!}\),
    其中 \(pre_i = \prod_{j = 1}^i (n - j)\),
    \(suf = \prod_{j = i}^k(n - j)\)

    复杂度大概:\(O(n^2tot)\)

    点击查看代码
    // #include <bits/stdc++.h>
    // using namespace std;
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <ctime>
    using namespace std;
    typedef long long ll;
    const int N = 305, S = 2222, MOD = 1e9 + 7;
    int n, a[N], b[N], num[N << 1], fac[N], ifac[N], id[N][N], cnt, len, f[S][N], t, tmp[N];
    struct segment { int l, r; } seg[S];
    bool vis[S];
    int Qpow(int a, int p)
    {
        int res = 1;
        while(p)
        {
            if(p & 1) res = (ll)res * a % MOD;
            a = (ll)a * a % MOD; p >>= 1;
        }
        return res;
    }
    void Get(int l, int r)
    {
        if(l > r || id[l][r]) return;
        id[l][r] = ++cnt; seg[cnt] = (segment){l, r};
        int mid = l + r >> 1;
        for(int i = max(l, mid - 2); i <= min(r, mid + 2); i++) if(abs(i + i - l - r) <= 2)
            Get(l, i - 1), Get(i + 1, r);
    }
    void Solve(int l, int r)
    {
        if(l > r || vis[id[l][r]]) return;
        vis[id[l][r]] = true;
        int mid = l + r >> 1;
        for(int i = max(l, mid - 2); i <= min(r, mid + 2); i++) if(abs(i + i - l - r) <= 2 && a[i] <= t && t < b[i])
        {
            Solve(l, i - 1); Solve(i + 1, r);
            for(int j = 1; j <= len; j++)
                f[id[l][r]][j] = (f[id[l][r]][j] + (ll)f[id[l][i - 1]][j] * f[id[i + 1][r]][j - 1]) % MOD;
        }
        for(int j = 1; j <= len; j++) if((f[id[l][r]][j] += f[id[l][r]][j - 1]) >= MOD) f[id[l][r]][j] -= MOD;
    }
    void Lagrange(int l, int r)
    {
        if(r - l <= n)
        {
            for(int i = 1; i <= cnt; i++) f[i][0] = f[i][r - l + 1];
            return;
        }
        tmp[n + 1] = 1;
        for(int i = n; i; i--) tmp[i] = (ll)tmp[i + 1] * (r - l - i) % MOD;
        for(int i = 1; i <= cnt; i++) f[i][0] = 0;
        ll prod = 1;
        for(int i = l; i <= l + n; i++)
        {
            ll res = prod * tmp[i - l + 1] % MOD * ifac[i - l] % MOD * 
                      ((l - n + i) & 1 ? MOD - ifac[l + n - i] : ifac[l + n - i]) % MOD;
            for(int j = 1; j <= cnt; j++) f[j][0] = (f[j][0] + res * f[j][i - l + 1]) % MOD;
            prod = prod * (r - i) % MOD; 
        }
    }
    int main()
    {
        // freopen("robot.in", "r", stdin);
        // freopen("robot.out", "w", stdout);
        scanf("%d", &n); Get(1, n); // fprintf(stderr, "cnt = %d\n", cnt);
        for(int i = 1; i <= n; i++)
            scanf("%d %d", &a[i], &b[i]), num[++num[0]] = a[i], num[++num[0]] = ++b[i];
        sort(num + 1, num + num[0] + 1);
        num[0] = unique(num + 1, num + num[0] + 1) - num - 1;
        for(int i = 1; i <= n; i++)
            a[i] = lower_bound(num + 1, num + num[0] + 1, a[i]) - num, 
            b[i] = lower_bound(num + 1, num + num[0] + 1, b[i]) - num;
        fac[0] = 1;
        for(int i = 1; i < N; i++) fac[i] = (ll)fac[i - 1] * i % MOD;
        ifac[N - 1] = Qpow(fac[N - 1], MOD - 2);
        for(int i = N - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
        fill(f[0], f[0] + N, 1);
        for(t = 1; t < num[0]; t++)
        {
            len = min(n + 1, num[t + 1] - num[t]);
            memset(vis, false, sizeof(vis));
            for(int i = 1; i <= cnt; i++) Solve(seg[i].l, seg[i].r);
            Lagrange(num[t], num[t + 1] - 1);
            for(int i = 1; i <= cnt; i++) memset(f[i] + 1, 0, len * sizeof(int));
        }
        printf("%d\n", f[1][0]);
        fprintf(stderr, "%.8lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
        return 0;
    }
    
  • 相关阅读:
    JDK中Unsafe类详解
    JAVA并发理论与实践
    关于FastJSON
    指数退避算法
    MySQL多表关联查询效率高点还是多次单表查询效率高,为什么?
    App开放接口api安全性—Token签名sign的设计与实现
    使用Jmeter进行http接口性能测试
    短信验证登录实现流程
    使用 Postman 取得 Token 打另一隻 API
    SpringMVC拦截器HandlerInterceptor使用
  • 原文地址:https://www.cnblogs.com/dixiao/p/16154447.html
Copyright © 2020-2023  润新知