「NOIP 2020」微信步数(Luogu P7116)

题意:

有一个 \(k\) 维场地,第 \(i\) 维宽为 \(w_i\),即第 \(i\) 维的合法坐标为 \(1, 2, \cdots, w_i\)。

小 C 有一个长为 \(n\) 的行动序列,第 \(i\) 个元素为二元组 \((c_i, d_i)\),表示这次行动小 C 的坐标由 \((x_1, x_2, \cdots, x_{c_i}, \cdots, x_k)\) 变为 \((x_1, x_2, \cdots, x_{c_i} + d_i, \cdots, x_k)\)。

小 C 会事先将行动序列重复无限次。

接下来,小 C 会以场地中的每个整点为起点,按照行动序列走直到走出场地。小 C 想知道他一共会走几步。你只要求出答案对 \(10^9 + 7\) 取模的结果。

\(1 \le n \le 5 \times 10^5, 1 \le k \le 10, 1 \le w_i \le 10^9, d_i \in \{1, -1\}\)。

思路:

首先可以发现,走出场地的时刻就是路径的(高维)Rounding Box 超出场地范围的时刻。换句话说,我们只关心路径中每一维坐标的最小、最大值。

把起点分两类讨论:走不超过 \(n\) 步就走出去的,和不属于这一类的。

对于第一类,我们枚举走 \(i\) 步恰好走出去。方案数就是前 \(i - 1\) 步的 Rounding Box 在范围内的方案数,减去前 \(i\) 步的 Rounding Box 还在范围内的方案数。对于两者,合法的起点都在一个(高维)矩形里,可以用乘法计算方案数。

对于第二类,把总步数拆成最后一轮走的步数(在 \([1, n]\) 中)和之前走的步数(是 \(n\) 的整数倍)。

先看前者。对于一个第二类起点,我们枚举一个 “假起点”,满足 “真起点” 先走整数轮到这点,然后走 \(n + i\) 步恰好走出场地(\(1 \le i \le n\))。

注意,一个 “假起点” 的贡献是 \(i\) 乘上可能的 “真起点” 个数。

我们记走一整轮后,第 \(i\) 维坐标的增量为 \(\Delta x_i\)。方便起见,\(\Delta x_i \ge 0\) 且不全为 \(0\)。

设 “真起点” 为 \((a_1, a_2, \cdots, a_k)\),“假起点” 为 \((b_1, b_2, \cdots, b_k)\)。那么两点一定满足关系 \(a_i = b_i - c \Delta x_i\)(\(c \ge 0\))。

现在,先固定 “假起点”。可以发现 “真起点” 还需满足形如 \(a_i \ge l_i\) 的限制,所以真起点个数就是 \(\lfloor \min\{\frac{b_i - l_i}{\Delta x_i}\} \rfloor + 1\)。

然后考虑 “假起点”。它的范围也是两个矩形的差,所以可以差分算贡献。更特殊地,发现它的最小值恰为 \(l_i\),即它的范围形如 \(l_i \le b_i < r_i\)。设 \(m_i = r_i - l_i\),方案数可以写作:

\[\begin{aligned}
& \sum_{b_1 = l_1}^{r_1 - 1} \sum_{b_2 = l_2}^{r_2 - 1} \cdots \sum_{b_k = l_k}^{r_k - 1} \lfloor \min\{\frac{b_i - l_i}{\Delta x_i}\} \rfloor + 1 \\
= & \sum_{j_1 = 0}^{m_1 - 1} \sum_{j_2 = 0}^{m_2 - 1} \cdots \sum_{j_k = 0}^{m_k - 1} \lfloor \min\{\frac{j_i}{\Delta x_i}\} \rfloor + 1 \\
= & \sum_{t \ge 0} \sum_{j_1 = 0}^{m_1 - 1} \sum_{j_2 = 0}^{m_2 - 1} \cdots \sum_{j_k = 0}^{m_k - 1} [\lfloor \min\{\frac{j_i}{\Delta x_i}\} \rfloor \ge t] \\
= & \sum_{t \ge 0} \prod_{i = 1}^{k} \max(0, m_i - t \Delta x_i) \\
\end{aligned}
\]

下面只需考虑这个东西如何算。首先可以发现 \(t\) 大于等于一个值 \(\text{lim}\) 时乘积的结果就为 \(0\)。通过这个事实可以把 \(\max\) 去掉。再把式子看做关于 \(t\) 的多项式,即可推得:

\[\begin{aligned}
& \sum_{t \ge 0} \prod_{i = 1}^{k} \max(0, m_i - t \Delta x_i) \\
= & \sum_{t = 0}^{\text{lim} - 1} \prod_{i = 1}^{k} m_i - t \Delta x_i \\
= & \sum_{t = 0}^{\text{lim} - 1} \sum_{i = 0}^{k} f_i t^i \\
= & \sum_{i = 0}^{k} f_i \sum_{t = 0}^{\text{lim} - 1} t^i \\
\end{aligned}
\]

所以问题转化成一个经典问题:求 \(k\) 次方前缀和。至此,我们解决了前者的贡献。

现在考虑后者就简单了。我们枚举从起点开始走 \(t\) 整轮还没走出边界,然后给答案加上 \(n\) 乘上合法起点数。这里发现合法的起点还是矩形,且 \(t\) 每增加 \(1\) 矩形第 \(i\) 维的宽度就减少 \(\Delta x_i\)。也就是说后者的式子和前者长得一样,也可以用上述方法计算。

最终我们解决了问题,时间复杂度 \(O(n k^2)\)。

代码:

#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= int(b); i++)
#define per(i, a, b) for (int i = (a); i >= int(b); i--)
using namespace std; typedef long long ll;
const int maxn = 5e5, mod = 1e9 + 7; int n, k, w[10], c[maxn + 5], d[maxn + 5], dt[10], res, S[11][11], inv[12]; struct foo {
int z[10], l[10], r[10];
void reset() {
memset(z, 0, k << 2);
memset(l, 0, k << 2);
memset(r, 0, k << 2);
}
foo() {
reset();
}
int walk(int c, int d) {
z[c] += d;
if (z[c] < l[c] || z[c] > r[c]) {
l[c] = min(l[c], z[c]);
r[c] = max(r[c], z[c]);
return d;
}
return 0;
}
} F, B; inline void red(int &x) {
x += x >> 31 & mod;
} void prework(int n) {
S[0][0] = 1;
rep(i, 1, n) rep(j, 1, i) {
S[i][j] = (S[i - 1][j - 1] + ll(S[i - 1][j]) * j) % mod;
}
inv[1] = 1;
rep(i, 2, n + 1) {
inv[i] = ll(mod - mod / i) * inv[mod % i] % mod;
}
} int calc(int k, int n) {
int s = 0, c = 1;
rep(i, 0, k) {
c = ll(c) * max(0, n - i) % mod;
s = (s + ll(c) * inv[i + 1] % mod * S[k][i]) % mod;
}
return s;
} int work(int a[]) {
int lim = mod;
rep(i, 0, k - 1) if (dt[i]) {
lim = min(lim, (a[i] + dt[i] - 1) / dt[i]);
}
int dp[11] = { 1 };
rep(i, 0, k - 1) {
per(j, i, 0) {
dp[j + 1] = (dp[j + 1] + ll(mod - dt[i]) * dp[j]) % mod;
dp[j] = ll(a[i]) * dp[j] % mod;
}
}
int res = 0;
rep(i, 0, k) {
res = (res + ll(dp[i]) * calc(i, lim)) % mod;
}
return res;
} int main() {
// freopen("walk.in", "r", stdin);
// freopen("walk.out", "w", stdout);
scanf("%d %d", &n, &k);
prework(k);
rep(i, 0, k - 1) {
scanf("%d", &w[i]);
}
rep(i, 1, n) {
scanf("%d %d", &c[i], &d[i]), c[i]--;
if (F.walk(c[i], d[i]) && F.r[c[i]] - F.l[c[i]] <= w[c[i]]) {
int x = 1;
rep(j, 0, k - 1) if (j != c[i]) {
x = ll(x) * max(0, w[j] - F.r[j] + F.l[j]) % mod;
}
res = (res + ll(i) * x) % mod;
}
}
rep(i, 1, n) if (F.z[c[i]] < 0) {
d[i] = -d[i];
}
rep(i, 0, k - 1) if (F.z[i] < 0) {
F.z[i] = -F.z[i];
swap(F.l[i], F.r[i]);
F.l[i] = -F.l[i];
F.r[i] = -F.r[i];
}
B = F;
bool chk = true;
rep(i, 0, k - 1) {
dt[i] = B.z[i];
chk &= dt[i] == 0;
}
if (chk) {
bool ok = false;
rep(i, 0, k - 1) {
ok |= B.r[i] - B.l[i] >= w[i];
}
printf("%d\n", ok ? res : -1);
exit(0);
}
int a[10] = {};
rep(i, 1, n) {
if (F.walk(c[i], d[i]) && F.r[c[i]] - F.l[c[i]] <= w[c[i]]) {
bool ok = true;
rep(j, 0, k - 1) if (j != c[i]) {
ok &= w[j] - F.r[j] + F.l[j] > 0;
}
if (!ok) {
continue;
}
rep(j, 0, k - 1) if (j != c[i]) {
a[j] = w[j] - F.r[j] + F.l[j];
}
a[c[i]] = w[c[i]] - F.r[c[i]] + F.l[c[i]] + 1, res = (res + ll(i) * work(a)) % mod;
a[c[i]] = w[c[i]] - F.r[c[i]] + F.l[c[i]], res = (res + ll(mod - i) * work(a)) % mod;
}
}
rep(i, 0, k - 1) {
a[i] = max(0, w[i] - B.r[i] + B.l[i]);
}
res = (res + ll(n) * work(a)) % mod;
printf("%d\n", res);
return 0;
}

「NOIP 2020」微信步数(计数)的更多相关文章

  1. 「NOIP 2017」列队

    题目大意:给定一个 $n times m$ 的方阵,初始时第 $i$ 行第 $j$ 列的人的编号为 $(i-1) times m + j$,$q$ 次给出 $x,y$,让第 $x$ 行 $y$ 列的人 ...

  2. 「NOIP 2013」 货车运输

    题目链接 戳我 \(Solution\) 这一道题直接用\(kruskal\)重构树就好了,这里就不详细解释\(kruskal\)重构树了,如果不会直接去网上搜就好了.接下来讲讲详细过程. 首先构建\ ...

  3. 「BalticOI 2020」病毒

    AC自动机+DP最短路转移 怎么说呢,挺套路的,也不是太难,但是一上手会被大量的信息淹没思路,还是要注意关注主要信息,不要被一些细节卡住 由于抗体是要在基因序里面出现过,那么考虑把抗体的序列检出AC自 ...

  4. 「CSP-S 2020」动物园

    description luogu loj(暂无数据) solution 这道题作为T2,对选手们考试开始后先通看一遍所有题目的好习惯,以及判断究竟谁才是真正的签到题的重要能力进行了较好的锻炼, 特别 ...

  5. 「CSP-S 2020」儒略日

    description luogu loj(暂无数据) solution 这道题作为T1,对选手们仔细看清题目的好习惯,以及不为2h调试.5k代码而心态爆炸的重要能力进行了较好的锻炼, 特别准备的只有 ...

  6. Solution -「ZJOI 2020」「洛谷 P6631」序列

    \(\mathcal{Description}\)   Link.   给定一个长为 \(n\) 的非负整数序列 \(\lang a_n\rang\),你可以进行如下操作: 取 \([l,r]\),将 ...

  7. Solution -「JOISC 2020」「UOJ #509」迷路的猫

    \(\mathcal{Decription}\)   Link.   这是一道通信题.   给定一个 \(n\) 个点 \(m\) 条边的连通无向图与两个限制 \(A,B\).   程序 Anthon ...

  8. Solution -「NOI 2020」「洛谷 P6776」超现实树

    \(\mathcal{Description}\)   Link.   对于非空二叉树 \(T\),定义 \(\operatorname{grow}(T)\) 为所有能通过若干次"替换 \( ...

  9. Solution -「FJWC 2020」人生

    \(\mathcal{Description}\)   OurOJ.   有 \(n\) 个结点,一些结点有染有黑色或白色,其余待染色.将 \(n\) 个结点染上颜色并连接有向边,求有多少个不同(结点 ...

随机推荐

  1. C++的继承权限

    原文来自于:https://www.cnblogs.com/2018shawn/p/10648408.html 公式: 继承成员对外的访问属性 = Max{继承方式,父类成员访问级别}: ps;以下成 ...

  2. FZU 2082 过路费(树链剖分 边权)题解

    题意:给出每条边权值,可以更新每条边权值,询问两个点路径的最小权值 思路:重链剖分边权化点权,让每个儿子节点继承边权. 插点权的时候比较边的两个节点的深度,插进儿子节点中. 代码: #include& ...

  3. Code Spell Checker & VSCode 单词拼写验证

    Code Spell Checker & VSCode 单词拼写验证 https://marketplace.visualstudio.com/items?itemName=streetsid ...

  4. Xcode 切换 iOS 模拟器的 Dark 模式

    Xcode 切换 iOS 模拟器的 Dark 模式 SwiftUI // // ContentView.swift // MemorizeGame // // Created by 夏凌晨 on 20 ...

  5. Internationalization API & ECMA-402

    Internationalization API & ECMA-402 i18n https://caniuse.com/?search=Internationalization API In ...

  6. Array.fill & String.padStart & String.padEnd

    Array.fill & String.padStart & String.padEnd Array.fill arr.fill(value[, start[, end]]) http ...

  7. cache-control config & http cache storage location control

    cache-control config & http cache storage location control cache-control 设置 where is the storage ...

  8. qt 向窗口发送消息,键盘输入事件

    #include <windows.h> #include <QtDebug> #include <locale> #include <tchar.h> ...

  9. NGK每日快讯」2021.1.27日NGK公链第85期官方快讯!

  10. java安全管理器SecurityManager

    本文转载自java安全管理器SecurityManager 导语 这是一篇对Java安全管理器入门的文章,目的是简单了解什么是SecurityManager,对管理器进行简单配置,解决简单问题. 比如 ...