1. 程式人生 > 實用技巧 >1118考試總結 擴充套件盧卡斯證明 分數規劃總結

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}\), 證明方法通分一下就好了, 這裡就不贅述了.

​ 所以說我們找的最優路徑肯定只是單個環, 而不是"環連環". 思想其實和上題差不多的.

這種方法應該有個名字, 叫分數規劃