「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. MySQL 基础面试题

    请写出什么是事务? 事务是一组不可分割的 DML 语句,事务处理可以用来维护数据库的完整性,保证一组 SQL 语句要么全部执行成功,要么全部不执行,只有 InnoDB 存储引擎才支持事务 . 事务的特 ...

  2. gradle中的build script详解

    目录 简介 project和task 一个例子 task详细讲解 task脚本 task依赖 动态task 默认task build script的外部依赖 gradle中的build script详 ...

  3. Ubuntu pppoeconf失败

    之前是通过sudo pppoeconf一路yes就可以连通有线网络(dsl和ethernet)的, 系统再次瘫痪后终于进入图形界面, 有线网络丢失, sudo pppoeconf也fail了, 其实加 ...

  4. 通过改写 原始对象的paint 方法,来设置对象的border颜色

    解决方案: //通过改写 原始对象的paint 方法,来设置对象的border颜色 1. package test1;import java.awt.Color;import java.awt.Gra ...

  5. JavaScript & Atomics

    JavaScript & Atomics Atomics 对象提供了一组静态方法对 SharedArrayBuffer 和 ArrayBuffer 对象进行原子操作. Atomics.add ...

  6. vue watch & arrow function bug

    vue watch & arrow function bug watch: { GeoJSON: function(newValue, oldValue) { log(`\n\n\nGeoJS ...

  7. SameSite & Cookies

    SameSite & Cookies SameSite=None && Secure (HTTPS) https://developer.mozilla.org/en-US/d ...

  8. nasm astrrchr函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...

  9. django学习-15.ORM查询方法汇总

    1.前言 django的ORM框架提供的查询数据库表数据的方法很多,不同的方法返回的结果也不太一样,不同方法都有各自对应的使用场景. 主要常用的查询方法个数是13个,按照特点分为这4类: 方法返回值是 ...

  10. Python安装教程

    1.下载好Python安装包后,双击打开(第一个是32位,第二个是64位,根据自己电脑位数进行选择): 2.打开后如下,先将下方的Python添加到系统环境变量勾选上,再点击第一个默认安装即可: 3. ...