1. 程式人生 > 其它 >Codeforces 1540C Converging Array

Codeforces 1540C Converging Array

\(a_i\)\(\frac{a_i + a_{i + 1} - b_i}{2}\) 做個比較,發現前者小於等於後者的條件為 \(a_{i + 1} - a_{i} \ge b_i\)

\(a_{i + 1}\)\(\frac{a_i + a_{i + 1} + b_i}{2}\) 做個比較,發現前者大於等於後者的條件為 \(a_{i + 1} - a_i \ge b_i\)

發現這兩個條件是一樣的,也就是說,一次操作的本質是:

  • 如果 \(a_{i + 1} - a_i \ge b_{i}\),則不會發生變化。
  • 如果 \(a_{i + 1} - a_i < b_i\)
    ,則發生變化。

更近一步的,如果發生了一次變化,那相鄰兩個數的差會變成什麼呢?

\[\frac{a_{i} + a_{i + 1} + b_{i}}{2} - \frac{a_{i} + a_{i + 1} - b_{i}}{2} = b_i \]

也就是說,相鄰的兩個數的差,從原本的 \(< b_i\) 變成了 $ = b_i$,且這兩個數的和不變

\(f_{1 \cdots n}\) 表示最終收斂的陣列,必然對任意 \(i\) 都存在 \(f_{i+1} - f_i \ge b_i\)

並且如果 \(f_{i + 1} - f_i > b_i\),則整個過程中,\(a_i\)

\(a_{i + 1}\) 之間就從來沒有發生過賦值操作。

這是因為,如果發生了操作,那麼這次操作後 \(f_{i + 1} - f_{i}\)就會變得等於 \(b_i\),而且我們知道兩個數的差只能從小於 \(b_i\) 變成等於 \(b_i\),而不能變成大於 \(b_i\),所以最終無法滿足 \(f_{i + 1} - f_i > b_i\) 的條件。

以滿足這種嚴格大於關係的點 \(i\) 為斷點,可以發現,\(a_{1 \cdots i}\)\(a_{i + 1 \cdots n}\) 是完全互相不影響的。

現在要解決最棘手的一個問題,如何求出 \(f_1\) 呢?

假設我們知道了第一個斷點的位置是 \(p\),那可以得到一個方程組:

\[\begin{cases} f_{2} - f_{1} = b_{1} \\ f_{3} - f_{2} = b_{2} \\ \cdots \\ f_{p} - f_{p - 1} = b_{p - 1} \\ \sum\limits_{i = 1}^{p} f_i = \sum\limits_{i = 1}^{p} a_i \end{cases} \]

\(f\) 看成未知數,則有 \(p\) 個未知數,\(p\) 個方程!於是我們可以手動解出 \(f_1\) 的值:

\[(f_1) + (f_1 + b_1) + (f_1 + b_1 + b_2) + \cdots = \sum_{i = 1}^{p} a_i \\ f_1 \times p + \sum_{i = 1}^{p - 1}(p - i)b_i = \sum_{i = 1}^{p} a_i \\ f_{1} = \frac{\sum\limits_{i = 1}^{p} a_i - \sum\limits_{i = 1}^{p - 1}(p - i)b_i}{p} \]

\(sa_p = \sum\limits_{i = 1}^{p}a_i, sb_p = \sum\limits_{i = 1}^{p - 1}(p - i)b_i\),則 \(f_1 = \frac{sa_p - sb_p}{p}\)

但問題是,我們並不知道 \(p\)是多少。正確的解決方法是,將每個位置都嘗試作為第一個端點,取解出來的 \(f_1\)最小值即可,也就是 \(f_1 = \min \left\{ \frac{s a_i - sb_i}{i} \right\}\)

首先證明 \(f_1 \ge \min \left\{ \frac{sa_i - sb_i}{i} \right\}\),這是因為必然存在某個字首 \(p\)使得 \(f_1 = \frac{sa_p - sb_p}{p}\),所以 \(f_1\)大於等於取任意 \(p\) 時的最小值。

然後證明 \(f_1 \le \min \left\{ \frac{sa_i - sb_i}{i} \right\}\),這是因為對於任意字首 \(p\),如果 \(f_1 \ne \frac{sa_p - sb_p}{p}\),就說明必然存在 \(f_{i + 1} - f_i > b_i\ (i < p)\) 的位置。但因為 \(\sum f\) 是保持不變的,為了使得跨度變大只能減小初值,得出 \(f_1 < \frac{sa_p - sb_p}{p}\)

回到原題,現在變成了一個計數問題。即,能找到多少個序列 \(a\),滿足 \(0 \le a_i \le c_i\),且 \(\min \left\{ \frac{s a_i - sb_i}{i} \right\} \ge x\)

\(\min\) 轉化為任意,命題等價於,對任意字首 \(i\),都要滿足:

\[\frac{sa_i - sb_i}{i} \ge x \quad \Rightarrow \quad sa_i \ge i \cdot x + sb_i \]

\(q = 1\) 時,不等式右邊都是已知量,考慮一個 DP,用 \(f_{i, j}\) 表示前 \(i\) 項,和為 \(j\) 的方案數。因為合法的 \(a\) 必然是一個字尾,使用字首和優化轉移即可,單次轉移 \(O(1)\)

時間複雜度 \(O(qn^2m)\)\(m\)\(a\)的上限。

現在考慮多組詢問,不同的 \(x\) 是否存在等價關係呢?

  • 如果存在 \(i\),滿足 $ i \cdot x + sb_i > n \cdot m$,即 \(x > \min\left\{\frac{n \cdot m - sb_i}{i}\right\}\),答案必然為 \(0\)
  • 如果任意 \(i\),滿足 \(i \cdot x + sb_i \le 0\),即 \(x \le \min\left\{ \frac{-sb_i}{i} \right\}\),答案必然為 \(\prod(c_i + 1)\)

現在只需要特別考慮 \(x \in \left(\min\left\{ \frac{-sb_i}{i} \right\}, \min\left\{\frac{n \cdot m - sb_i}{i}\right\}\right]\) 即可。

於是只有 \(O(m)\)種本質不同的詢問,全部預處理丟 map裡即可。時間複雜度 \(O(n^2m^2 + q \log m)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105, MOD = 1e9 + 7;
int n, f[N * N], g[N * N], b[N], sb[N], c[N];
map<int, int> ans;
int Solve(int x)
{
	fill(g, g + N * N, 1);
	for(int i = 1, sumc = c[1], lim; i <= n; i++, sumc += c[i]) 
	{
		lim = i * x + sb[i]; memset(f, 0, sizeof(f));
		for(int j = max(0, lim); j < N * N; j++) f[j] = (g[j] - (j - c[i] - 1 >= 0 ? g[j - c[i] - 1] : 0) + MOD) % MOD;
		memset(g, 0, sizeof(g)); g[0] = f[0];
		for(int j = 1; j < N * N; j++) g[j] = (g[j - 1] + f[j]) % MOD;
	}
	return g[N * N - 1];
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	int prod = 1;
	for(int i = 1; i <= n; i++) { cin >> c[i]; prod = (ll)prod * (c[i] + 1) % MOD; }
	for(int i = 1; i < n; i++) cin >> b[i];
	for(int i = 1; i <= n; i++) for(int j = 1; j < i; j++) sb[i] += (i - j) * b[j];
	int lb = 0, rb = INT_MAX, m = *max_element(c + 1, c + n + 1);
	for(int i = 1; i <= n; i++) lb = min(lb, -((sb[i] - 1) / i + 1)), rb = min(rb, (n * m - sb[i] - 1) / i + 1);
	for(int i = lb; i <= rb; i++) ans[i] = Solve(i);
	int q; cin >> q;
	while(q--)
	{
		int x; cin >> x;
		if(x >= lb && x <= rb) cout << ans[x] << endl;
		else if(x > rb) cout << 0 << endl;
		else if(x < lb) cout << prod << endl;
	}
	return 0;
}