[NOIP2020]微信步数[题解]

微信步数

题目描述

小 C 喜欢跑步,并且非常喜欢在微信步数排行榜上刷榜,为此他制定了一个刷微信步数的计划。

他来到了一处空旷的场地,处于该场地中的人可以用 \(k\) 维整数坐标 \((a_1, a_2, \ldots , a_k)\) 来表示其位置。场地有大小限制,第 \(i\) 维的大小为 \(w_i\),因此处于场地中的人其坐标应满足 \(1 \le a_i \le w_i\)\(1 \le i \le k\))。

小 C 打算在接下来的 \(P = w_1 \times w_2 \times \cdots \times w_k\) 天中,每天从场地中一个新的位置出发,开始他的刷步数计划(换句话说,他将会从场地中每个位置都出发一次进行计划)。

他的计划非常简单,每天按照事先规定好的路线行进,每天的路线由 \(n\) 步移动构成,每一步可以用 \(c_i\)\(d_i\) 表示:若他当前位于 \((a_1, a_2, \ldots , a_{c_i}, \ldots, a_k)\),则这一步他将会走到 \((a_1, a_2, \ldots , a_{c_i} + d_i, \ldots , a_k)\),其中 \(1 \le c_i \le k\)\(d_i \in \{-1, 1\}\)。小 C 将会不断重复这个路线,直到他走出了场地的范围才结束一天的计划。(即走完第 \(n\) 步后,若小 C 还在场内,他将回到第 \(1\) 步从头再走一遍)。

小 C 对自己的速度非常有自信,所以他并不在意具体耗费的时间,他只想知道 \(P\) 天之后,他一共刷出了多少步微信步数。请你帮他算一算。

【数据范围】

测试点编号 \(n \le\) \(k \le\) \(w_i \le\)
\(1 \sim 3\) \(5\) \(5\) \(3\)
\(4 \sim 6\) \(100\) \(3\) \(10\)
\(7 \sim 8\) \({10}^5\) \(1\) \({10}^5\)
\(9 \sim 12\) \({10}^5\) \(2\) \({10}^6\)
\(13 \sim 16\) \(5 \times {10}^5\) \(10\) \({10}^6\)
\(17 \sim 20\) \(5 \times {10}^5\) \(3\) \({10}^9\)

对于所有测试点,保证 \(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\}\)

分析

考虑将起点合并在一起移动,定义 \(l_i\) 为第 \(i\) 维向左的最大偏移量,\(r_i\) 为第 \(i\) 维向右的最大偏移量。

每次移动的贡献为本次移动前存活起点的数量。即 \(\prod_{i = 1}^k w_i - (r_i - l_i)\)。这是因为对于每一维来说,闭区间 \([1,-l_i]\)\([n - r_i + 1, n]\) 范围内的起点已经被淘汰了,这不难理解。只需注意当某一维满足 \(r_i - l_i >= w_i\) 时,说明这一维上所有点对应的起点都已经死亡,对于我们来说,也意味着统计结束。

判断无解的情况亦不难,我们发现,只要 \(n\) 次移动后任意一维存在偏移量 且中途没有走出场地,即为无解。

暴力求解的复杂度是 \(O(Tnk)\) 的,其中 \(T\) 取决于移动的轮次,在这里,排除无解后至少会存在一维有 \(1\) 的偏移量,最坏情况下即需要 \(max_{1\leq i\leq k}\{w_i \}\) 轮。

而显然,这样的复杂度是不足以通过此题的,考虑在这个基础上进行优化

注意到我们复杂度的瓶颈事实上主要在于轮次,考虑从这里入手。

在暴力程序的求解中,我们能够隐约感觉到每 \(n\) 次移动后存在着一定的周期性。事实上也正是如此。

略微扩充我们上面的定义,设 \(l_{i,j}\) 表示第一轮第 \(i\) 步第 \(j\) 维向左偏移的最大值,\(r_{i,j}\) 表示第一轮第 \(i\) 步第 \(j\) 维向右偏移的最大值。

设第一轮第 \(i\) 维总的偏移量为 \(e_i\),则对于第二轮的第 \(i\) 步,向左的最大偏移量事实上应该是 \(min(l_{i,j}, e_j + l_{i,j})\),向右的最大偏移量为 \(max(r_{i,j},e_j+r_{i,j})\)

而只要 \(e_i \neq 0\),第二轮总会有新死亡的起点。

如果我们将第一轮的最大偏移量作为边界,求第二轮的变化范围,则有:

\[l_{i,j} = min(0,l_{i,j}+e_j-l_{n,j}) \]

\[r_{i,j} = max(0,r_{i,j}+e_j-r_{n,j}) \]

这也不难理解,\(e_j\) 相当于初始位置,而 \(l_{i,j}\) 相当于第二轮在第 \(i\) 步第 \(j\) 维的向左最大偏移量,减去 \(l_{n,i}\) 即算差值,即新增的死去起点。\(r_{i,j}\) 同理。

那么,现在的 \(r_{i,j} - l_{i,j}\),即第二轮中 \(1\) \(∼\) \(i\) 步第 \(j\) 维新死掉的起点。

而有一个显然的事实,除了第一轮外,之后的所有轮次每步的死亡情况是一致的,一个感性的理解是,其他轮都存在被上一轮扩张过的地方,死过的起点不会再死一次,只有第一轮不存在这种情况。

那么,我们单独计算第一轮,再考虑从第二轮开始的其他轮次。

设第一轮后第 \(i\) 维还存活 \(a_i\) 个起点,每轮结束第 \(i\) 维都有 \(b_i\) 个起点死亡,最后一轮 \(1\) \(∼\) \(i\) 步第 \(j\) 维一共死了 \(g_{i,j} = r_{i,j} - l_{i,j}\) 个起点。

那么,在第 \(x+2\) 轮的第 \(i\) 步,第 \(j\) 维还存活着 \(a_j - x\times b_j - g_{i,j}\) 个起点,贡献为 \(\prod_{j = 1} ^ m a_j - x\times b_j - g_{i,j}\)

\(T = min_{1\leq j\leq m}\{\lfloor \frac{a_j-g_{i,j}}{b_j}\rfloor \}\),外层枚举 \(i\),内层枚举 $x = $ \(0\) \(∼\) \(T\),那么我们要算的事实上是:

\[\sum_{i = 1} ^ n\sum_{x =0}^T \prod_{j = 1}^m a_j - x\times b_j - g_{i,j} \]

内层 \(\prod\) 展开后,是一个关于 \(x\)\(m\) 次多项式,这个多项式的系数我们可以通过 \(O(k^2)\) 暴力计算。之后对于多项式的每一个单项 \(p_i x ^k\),只需要计算 \(\sum_{x = 0} ^ T x^k\) 即可。

关于 \(\sum_{i = 1}^n i^k\),可以使用拉格朗日插值法求解,时间复杂度 \(O(k)\)

总体时间复杂度 \(O(nk^2)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, M = 20, mod = 1e9 + 7, INF = 1e15;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
int n, m, ans;
int c[N], d[N];
int a[M], b[M], h[M], f[M][M];
int w[M], e[M], l[N][M], r[N][M];
int y[N], pre[N], suf[N], fac[N];
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k & 1) res = res * x % mod;
		k >>= 1, x = x * x % mod;
	}
	return res;
}
inline void initial()
{
	fac[0] = 1;
	for(register int i = 1; i <= N - 1; i++) fac[i] = fac[i - 1] * i % mod;
}
inline int Lagrange(int x, int z)
{
	int res = 0;
	pre[0] = 1, suf[z + 3] = 1;
	for(register int i = 1; i <= z + 2; i++) pre[i] = pre[i - 1] * ((x - i + mod) % mod) % mod;
	for(register int i = z + 2; i >= 1; i--) suf[i] = suf[i + 1] * ((x - i + mod) % mod) % mod;
	for(register int i = 1; i <= z + 2; i++){
		int a = pre[i - 1] * suf[i + 1] % mod, b = fac[i - 1] * fac[z + 2 - i] % mod;
		if((z + 2 - i) % 2) b = mod - b;
		res = res + y[i] * a % mod * power(b, mod - 2) % mod, res %= mod;
	}
	return res;
}
inline int calc(int a, int b)
{
	for(register int i = 1; i <= b + 2; i++) y[i] = (y[i - 1] + power(i, b)) % mod; 
	return Lagrange(a, b);
}
signed main()
{
	//freopen("P7116_17.in", "r", stdin);
	initial(); 
	n = read(), m = read(), ans = 1;
	for(register int i = 1; i <= m; i++)
		w[i] = read(), ans = ans * w[i] % mod;
	for(register int i = 1; i <= n; i++){
		c[i] = read(), d[i] = read(), e[c[i]] += d[i];
		for(register 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]]);
		int res = 1; //求取第一轮的贡献 
		for(register int j = 1; j <= m; j++)
			res = res * max((int)0, (w[j] - (r[i][j] - l[i][j]))) % mod;
		ans = (ans + res) % mod;
	}
	for(register int i = 1; i <= m; i++)
		if(e[i] || r[n][i] - l[n][i] >= w[i]) goto NEX; //能够走出去 
	puts("-1"); return 0;
	NEX:;
	for(register int i = 1; i <= m; i++) a[i] = w[i] - (r[n][i] - l[n][i]); //第一轮留下的贡献 
	for(register int i = 1; i <= n; i++) //每一轮的每一次移动新死亡起点的个数 
		for(register int j = 1; j <= m; j++)
			r[i][j] = max((int)0, r[i][j] + e[j] - r[n][j]), l[i][j] = min((int)0, l[i][j] + e[j] - l[n][j]); //第二轮范围更新
	for(register int i = 1; i <= m; i++) b[i] = r[n][i] - l[n][i]; //此后每一轮偏移的数
	for(register int i = 1, lst = -1; i <= n; i++){
		for(register int j = 0; j <= m; j++) f[0][j] = 0;
		f[0][0] = 1;
		int t = INF;
		for(register int j = 1; j <= m; j++){ 
			int x = a[j] - (r[i][j] - l[i][j]);
			if(x <= 0) goto END; //第二轮结束 
			if(b[j] > 0) t = min(t, x / b[j]);
			for(register 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 != lst){ //重新算系数 
			lst = t;
			for(register int j = 1; j <= m; j++) h[j] = calc(t, j);
		}
		for(register int j = 1; j <= m; j++)
			ans += h[j] * f[m][j] % mod, ans = (ans % mod + mod) % mod;
	}
	END:;
	printf("%lld\n", ans);
	return 0;
}
posted @ 2022-05-25 11:05  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(202)  评论(0编辑  收藏  举报