题解 P7116 【微信步数】

OMG_wc

2020-12-15 04:23:28

Solution

称 $n$ 步为一轮,首先 $-1$ 的情况很好判断:一轮后回到原地且在第一轮里存在某个起点走不出去。 我们把要求的答案转换一下:原本是考虑每个起点各自走多少步出界,现在转换成同时考虑所有起点,把每天还 **存活的起点** 数量计入贡献。(这里存活就是指从该起点出发到某天还没出界) 显然,只要把第 $0$ 天活着的起点算进去(也就是 $\prod w_i$),就和要算的答案等价了。 一共 $m$ 个维度,每个维度存活的位置是独立的,并且应是一段区间(只有开头、结尾的一部分会死亡)。 如果第 $j$ 维存活的区间是 $[l_j,r_j]$,那总共存活的数量就为 $\prod\limits_{j=1}^m(r_j-l_j+1)$ 。 根据这个想法,可以得到一个 $O(nmT)$ 的算法,其中 $T$ 最长轮数,最坏情况下为 $\max(w_j)$ 。 以下代码实测可以拿 $45$ 分: ```c++ 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; } ``` 下面考虑第 $j$ 维: 在第一轮第 $i$ 步时,历史移动最大位移为 $[l_i,r_i]$,那么死亡的起点数量应该为 $r_i-l_i$ 个。 这是因为 $[1,-l_i]$ 和 $[n-r_i+1,n]$ 范围内的起点已经死了。 假设第一轮总偏移量为 $e_j$,在第二轮第 $i$ 步时,历史移动最大位移应为 $[\min(l_i,e_j+l_i),\max(r_i,e_j+r_i)]$。 只要 $e_j\neq 0$,无论 $e_j$ 的正负,会有起点在第二轮是新死的。 把第一轮结束时的最大位移 $[l_n,r_n]$ 作为边界,求第二轮第 $i$ 步时的左右扩张范围 $[l'_i,r'_i]$,只需如下计算: ```c++ 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]); ``` 那么 $r'_i-l'_i$ 就是第二轮中 $1\sim i$ 步里新死的人。 容易发现一个事实:第 $2,3,4\cdots$ 轮里每步的死亡情况是一致的,只有第一轮是特殊的。(可以画个图理解下,除了第一轮外,其他轮都存在被上一轮已经扩张过的地方,死过的起点不会再死一次) 有了这个周期规律就可以优化了: 首先第一轮单独算,只考虑第二轮开始的。 设第一轮后还活着 $a_j$ 个起点,接下来每轮结束都有 $b_j$ 个起点死亡,最后一轮的 $1\sim i$ 步一共死了 $f_i=r'_i-l'_i$ 个点。 那么可以得到在 $x+2$ 轮的第 $i$ 步时,第 $j$ 维还活着 $a_j-x\times b_j-f_i$ 个点,贡献为 $\prod\limits_{j=1}^m a_j-x\times b_j-f_i$。 设 $T=\min\limits_{j=1}^m\frac{a_j-f_i}{b_j}$,那么我们需要外层枚举 $i$ ,内层枚举 $x=0,1,2,\ldots,T$ 。 (注意这里可能出现 $a_j-f_i\le 0$ 的情况,说明第二轮这个维度的起点就死光了,那后面就不用算了) 如果老老实实这样枚举 $x$ ,和之前做法就一样了。 要算的其实是 $\sum\limits_{i=1}^n\sum\limits_{x=0}^T\prod\limits_{j=1}^m a_j-x\times b_j-f_i$ 内层 $\prod$ 展开后,得到一个关于 $x$ 的 $m$ 次多项式 $G(x)$,这个多项式系数可以暴力 $O(m^2)$ 来算(我这里没有优化) 然后对多项式每项 $p_ix^k$,只要单独算 $\sum\limits_{x=0}^T x^k$ 即可。 关于计算 $\sum\limits_{i=1}^n i^k$ 参考 [传送门](https://www.luogu.com.cn/problem/CF622F)。 而对本题而言, $k\le 3$ 直接用公式,而 $k > 3$ 时,$n$ 不超过 $10^6$ 直接预处理也可以。 时间复杂度 $O(nm^2)$,代码如下: ```c++ #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; } ```