Description
給定 \(n\) 條邊,第 \(i\) 條邊的長度爲 \(i\),每條邊都有 \(50\%\) 的概率被選擇,求如果選出的邊能組成一個平面凸多邊形,則方案的權值是方案中邊的數量,否則權值爲 \(0\)。求權值的期望對大質數取模的值。
有 \(T\) 組數據。
Limitations
\(1 \leq n \leq T \leq 5000\)
Solution
因爲 \(\mathsf {\color{black} d}\mathsf{\color{red} {isangan233}}\) 在 yLOI 的時候用倒數第二檔子任務的做法過了C題,所以我也要在 MtOI 用倒數第二檔子任務的做法過掉他的 C題
注意到因爲所有情況的概率是相同的,因此只需要求出所有情況的總邊數,除以 \(2^n\) 即爲期望。
考慮 DP。
根據平面幾何的某定理,對於 \(n\) 條線段,它們能組成一個平面凸多邊形的充要條件是任意 \((n-1)\) 條線段的長度之和大於剩下一條線段的長度。
證明上必要性可以通過兩點之間線段最短的公理證得,充分性可以對選擇的線段數進行數學歸納。
得到推論:對於本題,選出的邊能組成平面凸多邊形的充要條件是最長的邊的長度小於其他邊長度之和。
證明上,考慮將最長的邊換成其它的邊,該邊長度變小,剩餘邊長度之和變大,不等號方向不會改變。
設 \(f_i\) 是所有邊的長度都不超過 \(i\) 時的所有能構成凸多邊形情況的邊數和。
考慮這些情況一共分兩類,第一類是不包括長度爲 \(i\) 的邊的情況,第二類是包括長度爲 \(i\) 的邊的情況。
對於第一類情況,邊數和就是 \(f_{i - 1}\)。
對於第二類情況,考慮剩下的邊長度之和只要大於 \(i\) 即可。
設 \(g_j\) 是選至少兩條邊且邊的長度和爲 \(j\) 時(不要求構成凸多邊形)所有情況的邊數和,\(h_j\) 是選至少兩條邊且邊的長度和爲 \(j\) 時(不要求構成凸多邊形)的總情況數。這兩個值均要求所選的邊的長度不超過 \(i\),實際上是省略了這兩個值的第一維。
因此有
\[f_{i} = f_{i - 1} + \sum_{j = i + 1}^{i^2} (g_j + h_j)\]
這裏加 \(h_j\) 是因爲對於每種情況都可以加一條長度爲 \(i\) 的邊來構成一個凸多邊形,對於所有情況,每種情況可以加一條邊,一共可以加 \(h_j\) 條邊,而 \(g_j\) 是這種情況原有的邊數。
考慮遞推 \(g\) 和 \(h\)。
考慮從小到大枚舉 \(i\),即邊長上限增加時,\(g\) 和 \(h\) 的變化。
對於 \(g_j\),所有任選兩條邊且邊權和爲 \((j - i)\) 的情況,都可以加入這條邊來達到邊權和爲 \(j\),同時還有不選擇加入長度爲 \(i\) 的邊這種情況,因此有
\[g_j = g_j + g_{j - i} + h_{j - i}~~~~~(j > i)\]
當然,對於選 \(i\) 和另一條邊的情況,也能對 \(g\) 產生貢獻,因此有
\[g_{j + i} = g_{j + i} + 2~~~~~~(j < i)\]
對 \(h\) 的遞推同理:
\[h_j = h_j + h_{j - i}~~~~~(j > i)\]
\[h_{j + i} = h_{j + i} + 1~~~~~(j < i)\]
然後用得到的 \(g\) 和 \(h\) 遞推 \(f\) 即可。
考慮複雜度:所有邊之和是 \(O(n^2)\) 級別的,因此每次更新 \(g\) 和 \(h\) 都是 \(O(n^2)\) 的,一共更新 \(O(n)\) 次,因此更新 \(g\) 和 \(h\) 的總複雜度 \(O(n^3)\)。而遞推 \(f\) 時每次也是 \(O(n^2)\) 的,一共更新 \(O(n)\) 次,所以複雜度也是 \(O(n^3)\)。因此總時間複雜度 \(O(n^3)\),可以通過前 \(4\) 個子任務。
但是注意到我們的空間複雜度是 \(O(n^2)\) 的,因此對於第 \(5\) 個子任務,我們的空間完全能夠承受,同時時間複雜度仍然是多項式級,因此 本地掛機打表 即可通過子任務 \(5\),表的長度是 \(48k\),甚至不需要用字符加密來縮短代碼長度,直接存明文即可。
Code
#include <cstdio>
#include <algorithm>
const int maxn = 100005;
const int MOD = 1000000007;
const int MODP = 1000000005;
int T, N;
int query[maxn];
ll frog[maxn], gorf[maxn], h[maxn];
ll mpow(const ll x, int y);
int main() {
freopen("1.in", "r", stdin);
qr(T);
for (int i = 1; i <= T; ++i) {
qr(query[i]);
N = std::max(N, query[i]);
}
gorf[3] = 2; h[3] = 1;
for (int i = 3, upceil = 3; i <= N; ++i) {
for (int j = i + 1; j <= upceil; ++j) {
(frog[i] += gorf[j] + h[j]) %= MOD;
}
(frog[i] += frog[i - 1]) %= MOD;
upceil += i;
for (int j = upceil; j >= i; --j) {
(gorf[j] += gorf[j - i] + h[j - i]) %= MOD;
(h[j] += h[j - i]) %= MOD;
}
for (int j = 1; j < i; ++j) {
(gorf[j + i] += 2) %= MOD;
(h[j + i] += 1) %= MOD;
}
}
for (int i = 1; i <= T; ++i) {
qw(frog[query[i]] * (mpow(mpow(2, query[i]), MODP)) % MOD, '\n', true);
}
return 0;
}
ll mpow(const ll x, int y) {
ll _ret = 1, _tmp = x;
while (y) {
if (y & 1) (_ret *= _tmp) %= MOD;
(_tmp *= _tmp) %= MOD;
y >>= 1;
}
return _ret;
}