「NOIP 2020」微信步數(計數)


「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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM