1. 程式人生 > 其它 >[NOIP2020]微信步數[題解]

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