1118考試總結 擴充套件盧卡斯證明 分數規劃總結
1118考試總結
T1
題目大意:
給定一個數列, 求第\(k\)大值.\(n <= 1e7\)
一看這資料範圍肯定不能用\(sort\)了, 考場上我用的二分法, 就是二分第\(k\)大值,看看有多少數比它小, 判斷一下是否有\(k\)個, 複雜度差不多是\(O(3e8)\)的, 勉強過去了.
#include <bits/stdc++.h> using namespace std; inline long long read() { long long s = 0, f = 1; char ch; while(!isdigit(ch = getchar())) (ch == '-') && (f = -f); for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48)); return s * f; } const int N = 1e7 + 5, mod = 1e9; int n, k, x, ans; long long y; int a[N]; int check(int x) { register int res = 0; for(register int i = 1;i <= n; ++ i) { if(a[i] <= x) res ++; if(res > k) return res; } return res; } int main() { n = read(); k = read(), a[1] = x = read(); y = read(); for(register int i = 2;i <= n; ++ i) a[i] = (y * a[i - 1] % mod + x) % mod; int l = 0, r = 1e9 + 1; while(l <= r) { register int mid = (l + r) >> 1; register int tmp = check(mid); if(tmp > k) r = mid - 1, ans = mid; else if(tmp == k) r = mid - 1, ans = mid; else l = mid + 1; } printf("%d", ans); return 0; }
還有另一種標準\(O(n)\)解法, 用遞迴求.
直接選取\(a[1]\)作為標準, 然後把這\(n\)個數按比\(a[1]\)小(d個), 和\(a[1]\)相等(count個), 比\(a[1]\)大分(e個)為三類.
如果說\(d < k, d + count >= k\), 說明找到了正解.
如果\(d >= k\),說明當前的標準找大了, 那麼就將比\(a[1]\)小的那些數的第一個作為標準, 然後遞迴.
如果\(d < k\),說明當前的標準找小了, 那麼就將比\(a[1]\)大的那些數的第一個作為標準, 然後遞迴, 注意下次找第\(k - count - d\)
複雜度經過數學分析是\(O(n)\)的.我不會.
#include <bits/stdc++.h> using namespace std; inline long long read() { long long s = 0, f = 1; char ch; while(!isdigit(ch = getchar())) (ch == '-') && (f = -f); for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48)); return s * f; } const int N = 1e7 + 5, mod = 1e9; int n, k, x, ans; long long y; int a[N], b[N], c[N]; void solve(int *a, int *b, int *c, int k, int n) { int tmp = a[1], count = 0, d = 0, e = 0; for(int i = 1;i <= n; i++) { if(a[i] == tmp) count ++; if(a[i] < tmp) b[++ d] = a[i]; if(a[i] > tmp) c[++ e] = a[i]; } if(d < k && d + count >= k) { printf("%d", tmp); return ; } else if(d >= k) solve(b, a, c, k, d); else solve(c, b, a, k - count - d, e); } int main() { n = read(); k = read(), a[1] = x = read(); y = read(); for(register int i = 2;i <= n; ++ i) a[i] = (y * a[i - 1] % mod + x) % mod; solve(a, b, c, k, n); return 0; }
T2
水題不說了.
T3
題目大意:
一個人有\(n\)個相同的蘋果, 要把這些蘋果裝到不同的盒子裡給另外一個人.這個人可以吃任意個蘋果, 這些盒子可以為空, 問總共有多少種方案.
\(n, m <= 1e9, p < 2^{31}\),p不一定為質數.
組合數 + 擴充套件盧卡斯定理.
化簡完題意後, 我們可以得出答案就是\(\displaystyle \sum_{i = 0}^{n} C_{i + m - 1}^{m - 1}\).
假設這個人吃完蘋果後還剩\(i\)個, 那麼方案數就是\(C_{i + m - 1}^{m - 1}\).可以用隔板法理解, 因為盒子可以空, 我們可以預先在\(m\)個盒子裡都墊上一個蘋果, 那麼現在就有\(i+ m\)個蘋果, 我們要把它們分到\(m\)個盒子裡, 那麼就需要在任意\(n + m - 1\)個空隙裡選出\(m - 1\)個空隙.
上式還可以化簡:\(C_{m - 1}^{m - 1} + C_m^{m - 1} + ... + C_{m + n - 1}^{m - 1} = C_{m}^{m} + C_m^{m - 1} + C_{m + 1}^{m - 1 } + ... + C_{n + m - 1}^{m - 1} = C_{m + 1}^{m} + C_{m + 1}^{m - 1} + ... + C_{n + m - 1}^{m - 1}= C_{n + m}^{m}\).
然後我們就可以開開心心求組合數.....等等, \(p\)不是質數, \(n,m <= 1e9\), woc毒瘤啊!
那怎麼辦, 我們知道唯一分解定理, 可以把p分解質因數, 然後可以求出組合數模每一個\(p_c^{k_c}\)的值, 然後用中國剩餘定理合併.(因為\(p_1^{k_1}, p_2^{k_2}...\)都兩兩互質, 所以可以合併).
又發現\(n, m\)過於大, 我們可以用盧卡斯定理求. 但是盧卡斯定理僅適用於\(p\)為質數的情況, 對於\(p_c^{k_c}\)這個不一定為質數的模數我們只能用擴充套件盧卡斯來求.
具體思路就是這樣, 由於剛剛學習擴充套件盧卡斯(其實講過好幾遍了, 剛剛才會...), 我認為有必要寫一下證明過程:
我們現在要求:$\displaystyle \frac{n!}{m!(n - m)!} % p^k \(, 由於不是質數, 不能求\)m!\(的逆元, 我們可以轉換一下形式使\)n!\(這些東西與模數互質, 於是就變成了:\)\displaystyle \frac{\frac{n!}{p^x}}{\frac{m!}{p^y}\frac{(n - m)!}{p^z}} * p^{x - y + z} % p^k $.
那麼現在問題轉化成了求\(\displaystyle \frac{n!}{p^x}\).
\(x\)很好求, \(x=\displaystyle \sum _{i=1, p^i <= n} \lfloor \frac{n}{p^i} \rfloor\).現在只需求\(\displaystyle \frac{n!}{p^x} \% p^k\).就好了.
假設當前\(n = 22, p = 3, k = 2, P=p^k\), 我們把\(n / P\)的整塊和\(n \% p\)的殘塊挑出來:
\((1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9) * (10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18) * (19 * 20 * 21 * 22)\)
然後再把這裡面所有\(p\)的倍數挑出來:
\((1 * 2 * 4 * 5 * 7 * 8) * (10 * 11 * 13 * 14 * 16 * 17) * (19 * 20 * 22) * 3^7*(1 * 2 * 3 * 4 * 5 * 6 * 7)\)
我們發現, 設一個整塊的乘積是\(X\), 那麼所有整塊的乘積與\(P\)取模的結果就是\(X^{n / P} \% P\).然後殘塊暴力算, \(3^7\)也可與模數消掉, 然後我們發現最後一塊又是一個階乘, 我們可以將其表示為:\(\displaystyle \frac{(n / p)!}{p^x} \% P\). 然後遞迴求解就好了.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int n_, m_, P, cnt, M, ans_;
int p[50], d[50], b[50], m[50], k[50], ans[50];
void divide() {
for(int i = 2;i * i <= P; i++) {
if(!(P % i)) {
p[++ cnt] = i;
while(!(P % i)) b[cnt] ++, P /= i;
}
}
if(P > 1) p[++ cnt] = P, b[cnt] = 1;
}
int ksm(int x, int y, int mod) {
int res = 1;
while(y) { if(y & 1) res = 1ll * res * x % mod; x = 1ll * x * x % mod; y >>= 1; }
return res;
}
void ex_gcd(int a, int b, long long &x, long long &y) {
if(!b) { x = 1; y = 0; return ; }
else { ex_gcd(b, a % b, y, x); y -= x * (a / b); return ; }
}
int inv(int a, int b) {
long long x, y;
ex_gcd(a, b, x, y);
return (x + b) % b;
}
void CRT() {
ans_ = 0; M = 1; long long x, y;
for(int i = 1;i <= cnt; i++) M = M * d[i];
for(int i = 1;i <= cnt; i++) m[i] = M / d[i], ex_gcd(d[i], m[i], x, y), k[i] = y;
for(int i = 1;i <= cnt; i++) ans_ = (ans_ + 1ll * ans[i] * m[i] % M * k[i] % M) % M;
ans_ = (ans_ + M) % M;
}
int main() {
n_ = read(); m_ = read(); P = read();
divide();
for(int i = 1;i <= cnt; i++) {
int x[3], y[3], now, P = ksm(p[i], b[i], 1e9);
x[1] = x[2] = x[0] = 0;
for(int j = 0;j < 3; j++) {
j == 0 ? now = n_ + m_ : j == 1 ? now = n_ : now = m_;
int u = p[i]; y[j] = 1;
while(u <= now) { x[j] += now / u; u *= p[i]; }
while(now) {
int tmp = 1;
for(int k = 1;k <= P; k++) if(k % p[i]) tmp = 1ll * tmp * k % P;
int div = now / P, yu = now % P;
y[j] = 1ll * y[j] * ksm(tmp, div, P) % P;
for(int k = P * div + 1;k <= P * div + yu; k++) if(k % p[i]) y[j] = 1ll * y[j] * k % P;
now /= p[i];
}
}
ans[i] = ksm(p[i], x[0] - x[1] - x[2], P);
ans[i] = 1ll * ans[i] * y[0] % P;
ans[i] = 1ll * ans[i] * inv(y[1], P) % P;
ans[i] = 1ll * ans[i] * inv(y[2], P) % P;
d[i] = P;
}
fclose(stdin); fclose(stdout);
return 0;
}
T4
題目大意:
小P可以求出任意一個數列的藝術價值,它等於將這個數列 順次劃分為若干個極長單調區間(相鄰兩個單調區間的單調性必須不相同)後,每個單 調區間中元素總和的平均值。比如對於數列3 7 9 2 4 5,它將被劃分為[3 7 9] [2] [4 5], 其藝術價值為(19 +2 + 9)/3 = 10。由於小P特殊的審美觀,他還要求劃分出的第一個單 調區間必須為單調增區間,也就是說,對於數列10 9 8,它將被劃分為[10] [9 8],而不 是[10 9 8]現在小P手裡有一個長度為n的序列a,,他想問你,這個序列的所有子序列中,藝術價值最大的是哪個子序列,輸出其藝術價值。注意:本題單調數列為嚴格單調,也就是說數列中的數必須嚴格上升或嚴格下降 \(n <= 1e5\)
最長上升子序列 + 樹狀陣列.
首先我們可以證明最後的結果只有兩種形式: 一直單調遞增 或者 先單調遞增後單調遞減 .
如果是這樣的:
我們可以算出總的答案是:\(\frac{ans1 + ans2}{2}\), 可以發現它小於max(ans1, ans2).
如果是這樣的:
假設\(ans1 >ans2\), 那麼我們會發現\(ans1 > \frac{2ans1 + ans2}{3}\), 也就是\(ans1\)比總體的答案要優.
假設\(ans1 < ans2\), 那麼我們會發現\(ans2 > \frac{2ans1 + ans2}{3}\),也就是\(ans2\)比總體的答案要優.
然後我們用\(f[i]\)表示從\(1\)到\(i\)的單調遞增的總和, \(g[i]\)表示從\(i\)到\(n\)的單調遞減的總和.就做完啦.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int n, a[N], b[N];
long long f[N], g[N], t[N];
double ans;
int lowbit(int x) { return x & (-x); }
long long query(int x) {
long long res = 0; for( ; x ; x -= lowbit(x)) res = max(res, t[x]); return res;
}
void insert(int x, long long y) {
for( ; x < N ; x += lowbit(x)) t[x] = max(t[x], y);
}
int main() {
n = read();
for(int i = 1;i <= n; i++) b[i] = a[i] = read();
sort(b + 1, b + n + 1);
int cnt = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1;i <= n; i++) a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
for(int i = 1;i <= n; i++) {
f[i] = query(a[i] - 1) + b[a[i]];
insert(a[i], f[i]);
}
memset(t, 0, sizeof(t));
for(int i = n;i >= 1; i--) {
g[i] = query(a[i] - 1) + b[a[i]];
insert(a[i], g[i]);
}
for(int i = 1;i <= n; i++) {
ans = max(ans, 1.0 * f[i]);
if(i != 1) ans = max(ans, 1.0 * (f[i] + g[i] - b[a[i]]) / 2);
}
printf("%.3lf\n", ans);
return 0;
}
這種思想的題其實還有好多比如這個:T1
也是通過一些證明得到了一個正確且容易求的結論.
還比如這個:
Makik 有一張詳細的城市地圖,地圖標註了 L 個景區,編號為 1~L。而景區與景區之間建有 單向高速通道。這天,Makik 要去逛景區,他可以任選一個景區開始一天行程,且只能通過單向高速通道進入其他景區。
至少要參觀兩個景區,遊玩最後要回到起始景區。如果 Makik 參觀了第 i 個景區,會獲得一個樂趣值 \(F_i\)。且參觀過得景區不會再獲得樂趣值。對於第 i 條單向高速通道,需要消耗 \(T_i\) 的時間,能夠從 \(L1_i\) 到達 \(L2_i\)。為了簡化問題,參觀景區不需要花費時間,Makik 想要最終單位時間內獲得的樂趣值最大。請你寫個程式,幫 Makik 計算一下他能得到的最大平均樂趣值。
簡化題意就是求一個最優的環.
我們設第一個環的總和為\(a\), 點的個數為\(b\), 第二個環的總和為\(c\), 點的個數為\(d\), 假設這兩個環連著並且沒用重複的點, 那我們可以得到一條新的總和為\(a + c\), 點的個數為\(b + d\)的路徑.
顯然, 如果說\(\frac{a}{b} > \frac{c}{d}\), 那麼可以得到\(\frac{a}{b} > \frac{a + c}{b + d} > \frac{c}{d}\), 證明方法通分一下就好了, 這裡就不贅述了.
所以說我們找的最優路徑肯定只是單個環, 而不是"環連環". 思想其實和上題差不多的.
這種方法應該有個名字, 叫分數規劃