1 条题解

  • 0
    @ 2025-8-24 22:26:48

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar OMG_wc
    幻想家协会会长

    搬运于2025-08-24 22:26:48,当前版本为作者最后更新于2020-12-15 04:23:28,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    nn 步为一轮,首先 1-1 的情况很好判断:一轮后回到原地且在第一轮里存在某个起点走不出去。

    我们把要求的答案转换一下:原本是考虑每个起点各自走多少步出界,现在转换成同时考虑所有起点,把每天还 存活的起点 数量计入贡献。(这里存活就是指从该起点出发到某天还没出界)

    显然,只要把第 00 天活着的起点算进去(也就是 wi\prod w_i),就和要算的答案等价了。

    一共 mm 个维度,每个维度存活的位置是独立的,并且应是一段区间(只有开头、结尾的一部分会死亡)。

    如果第 jj 维存活的区间是 [lj,rj][l_j,r_j],那总共存活的数量就为 j=1m(rjlj+1)\prod\limits_{j=1}^m(r_j-l_j+1)

    根据这个想法,可以得到一个 O(nmT)O(nmT) 的算法,其中 TT 最长轮数,最坏情况下为 max(wj)\max(w_j)

    以下代码实测可以拿 4545 分:

    int w[20], e[20], l[20], r[20];
    int c[N], d[N];
    int n, m;
    
    int main() {
        scanf("%d%d", &n, &m);
        LL ans = 1;
        for (int i = 1; i <= m; i++) {
            scanf("%d", &w[i]);
            ans = ans * w[i] % mod;
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &c[i], &d[i]);
        }
        while (1) {
            for (int i = 1; i <= n; i++) {
                e[c[i]] += d[i];
                l[c[i]] = min(l[c[i]], e[c[i]]);
                r[c[i]] = max(r[c[i]], e[c[i]]);
                LL s = 1;
                for (int j = 1; j <= m; j++) {
                    if (r[j] - l[j] >= w[j]) goto M1;
                   s = s * (w[j] - r[j] + l[j]) % mod;
                }
                ans = (ans + s) % mod;
            }
            bool lose = 1;
            for (int j = 1; j <= m; j++) {
                if (e[j] != 0) lose = 0;
            }
            if (lose) {
                ans = -1;
                break;
            }
        }
    M1:
        printf("%lld\n", ans);
        return 0;
    }
    

    下面考虑第 jj 维:

    在第一轮第 ii 步时,历史移动最大位移为 [li,ri][l_i,r_i],那么死亡的起点数量应该为 rilir_i-l_i 个。

    这是因为 [1,li][1,-l_i][nri+1,n][n-r_i+1,n] 范围内的起点已经死了。

    假设第一轮总偏移量为 eje_j,在第二轮第 ii 步时,历史移动最大位移应为 [min(li,ej+li),max(ri,ej+ri)][\min(l_i,e_j+l_i),\max(r_i,e_j+r_i)]

    只要 ej0e_j\neq 0,无论 eje_j 的正负,会有起点在第二轮是新死的。

    把第一轮结束时的最大位移 [ln,rn][l_n,r_n] 作为边界,求第二轮第 ii 步时的左右扩张范围 [li,ri][l'_i,r'_i],只需如下计算:

    r[i][j] = max(0, r[i][j] + e[j] - r[n][j]);
    l[i][j] = min(0, l[i][j] + e[j] - l[n][j]);
    

    那么 rilir'_i-l'_i 就是第二轮中 1i1\sim i 步里新死的人。

    容易发现一个事实:第 2342,3,4\cdots 轮里每步的死亡情况是一致的,只有第一轮是特殊的。(可以画个图理解下,除了第一轮外,其他轮都存在被上一轮已经扩张过的地方,死过的起点不会再死一次)

    有了这个周期规律就可以优化了:

    首先第一轮单独算,只考虑第二轮开始的。

    设第一轮后还活着 aja_j 个起点,接下来每轮结束都有 bjb_j 个起点死亡,最后一轮的 1i1\sim i 步一共死了 fi=rilif_i=r'_i-l'_i 个点。

    那么可以得到在 x+2x+2 轮的第 ii 步时,第 jj 维还活着 ajx×bjfia_j-x\times b_j-f_i 个点,贡献为 j=1majx×bjfi\prod\limits_{j=1}^m a_j-x\times b_j-f_i

    T=minj=1majfibjT=\min\limits_{j=1}^m\frac{a_j-f_i}{b_j},那么我们需要外层枚举 ii ,内层枚举 x=0,1,2,,Tx=0,1,2,\ldots,T

    (注意这里可能出现 ajfi0a_j-f_i\le 0 的情况,说明第二轮这个维度的起点就死光了,那后面就不用算了)

    如果老老实实这样枚举 xx ,和之前做法就一样了。

    要算的其实是 $\sum\limits_{i=1}^n\sum\limits_{x=0}^T\prod\limits_{j=1}^m a_j-x\times b_j-f_i$

    内层 \prod 展开后,得到一个关于 xxmm 次多项式 G(x)G(x),这个多项式系数可以暴力 O(m2)O(m^2) 来算(我这里没有优化)

    然后对多项式每项 pixkp_ix^k,只要单独算 x=0Txk\sum\limits_{x=0}^T x^k 即可。

    关于计算 i=1nik\sum\limits_{i=1}^n i^k 参考 传送门

    而对本题而言, k3k\le 3 直接用公式,而 k>3k > 3 时,nn 不超过 10610^6 直接预处理也可以。

    时间复杂度 O(nm2)O(nm^2),代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int INF = 0x3f3f3f3f;
    const LL mod = 1e9 + 7;
    const int N = 500005;
    
    LL pow_mod(LL x, LL n) {
        LL res = 1;
        while (n) {
            if (n & 1) res = res * x % mod;
            n >>= 1;
            x = x * x % mod;
        }
        return res;
    }
    
    LL fac[N];
    // 计算 1^m+2^m+3^m+...+n^m
    LL cal(LL n, LL m) {
        LL res = 0;
        if (n <= m + 2) {
            for (int i = 1; i <= n; i++) {
                res = (res + pow_mod(i, m)) % mod;
            }
        } else {
            fac[0] = 1;
            for (int i = 1; i <= m + 1; i++) {
                fac[i] = fac[i - 1] * i % mod;
            }
            LL t = 1;
            for (int i = 1; i <= m + 2; i++) {
                t = t * (n - i) % mod;
            }
            LL y = 0;
            int flag = (m + 2) % 2 ? 1 : -1;
            for (int i = 1; i <= m + 2; i++) {
                y = (y + pow_mod(i, m)) % mod;
                res += flag * y * t % mod * pow_mod(n - i, mod - 2) % mod * pow_mod(fac[i - 1] * fac[m + 2 - i] % mod, mod - 2) % mod;
                flag = -flag;
            }
            res = (res % mod + mod) % mod;
        }
        return res;
    }
    
    int w[20], e[20], l[N][20], r[N][20];
    int a[20], b[20], h[20];
    LL f[20][20];
    int c[N], d[N];
    int n, m;
    
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            scanf("%d", &w[i]);
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &c[i], &d[i]);
            e[c[i]] += d[i];
            for (int j = 1; j <= m; j++) {
                l[i][j] = l[i - 1][j];
                r[i][j] = r[i - 1][j];
            }
            l[i][c[i]] = min(l[i][c[i]], e[c[i]]);
            r[i][c[i]] = max(r[i][c[i]], e[c[i]]);
        }
        bool lose = 1;
        for (int i = 1; i <= m; i++) {
            if (e[i] != 0 || r[n][i] - l[n][i] >= w[i]) {
                lose = 0;
            }
        }
        if (lose) return puts("-1"), 0;
        for (int j = 1; j <= m; j++) {
            a[j] = w[j] - (r[n][j] - l[n][j]);
        }
        LL ans = 0;
        // 第一轮贡献
        for (int i = 0; i <= n; i++) {
            LL s = 1;
            for (int j = 1; j <= m; j++) {
                s = s * max(0, (w[j] - (r[i][j] - l[i][j]))) % mod;
            }
            ans = (ans + s) % mod;
        }
        // 第二轮的死亡范围更新
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                r[i][j] = max(0, r[i][j] + e[j] - r[n][j]);
                l[i][j] = min(0, l[i][j] + e[j] - l[n][j]);
            }
        }
        for (int j = 1; j <= m; j++) {
            b[j] = r[n][j] - l[n][j];
        }
        // 第二轮开始的贡献
    
        int last = -1;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) f[0][j] = 0;
            f[0][0] = 1;
            int t = INF;
            for (int j = 1; j <= m; j++) {
                int x = a[j] - r[i][j] + l[i][j];
                if (x <= 0) goto M1;  // 第二轮就暴毙了
                if (b[j] > 0) t = min(t, x / b[j]);
                for (int k = 0; k <= m; k++) {
                    f[j][k] = f[j - 1][k] * x % mod;
                    if (k > 0)
                        f[j][k] = (f[j][k] + f[j - 1][k - 1] * -b[j]) % mod;
                }
            }
            ans += f[m][0] * (t + 1) % mod;
            if (t != last) {
                last = t;
                for (int j = 1; j <= m; j++) h[j] = cal(t, j);
            }
            for (int j = 1; j <= m; j++) {
                ans += h[j] * f[m][j] % mod;
            }
        }
    M1:;
        ans = (ans % mod + mod) % mod;
        printf("%lld\n", ans);
        return 0;
    }
    
    • 1

    信息

    ID
    6306
    时间
    1000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者