WiwiHo
枚舉?
暴力 (X)
聰明的暴力 (O)
枚舉 \(k\) 和 \(x\)
代進去檢查會不會等於 \(n\)?
有 \(k\) 就可以算 \(x=\frac{n}{2^k-1}\)
有 \(x\) 就可以算 \(2^k-1=\frac{n}{x}\)
\(\Rightarrow\) 只要枚舉一個就好
題目要 \(x\),所以枚舉 \(x\)?
時間複雜度:\(O(n)\)
但 \(n=10^9\) 怎麼辦?
那就枚舉 \(k\) 吧
時間複雜度:\(O(\log n)\)
枚舉 \(2\sim n\) 的整數 \(k\)
再對整數 \(k\) 花 \(O(k)\) 時間暴搜有沒有因數
是質數的話再判斷是不是 \(n\) 的因數
時間複雜度:\(O(n^2)\)
有沒有辦法優化?
從判斷質數下手?
質數篩??
時間複雜度:\(O(n)\)
不要中毒
根本就不用判
從小到大枚舉 \(k\)
遇到 \(n\) 的因數
就不斷 \(n \gets \frac{n}{k}\)
直到 \(n\) 不被 \(k\) 整除為止
如果 \(k\) 不是質數且它是一開始的 \(n\) 的因數
因為是由小到大枚舉 \(k\)
而 \(k\) 的質因數小於 \(k\)
此時的 \(n\) 肯定不是 \(k\) 的倍數
時間複雜度: \(O(n)\)
可以更好嗎?
如果某數字 \(x\) 是合數
那它肯定有一個因數 \(\leq \sqrt{x}\)
\(\Rightarrow\) 只要從 \(1 \sim \sqrt{n}\) 枚舉 \(k\)
最後如果 \(n>1\)
那剩下來的 \(n\) 就也是本來的 \(n\) 的質因數
時間複雜度:\(O(\sqrt{n})\)
枚舉 \(C^n_k\) 個子序列
先枚舉第一個元素
再枚舉第二個……
再枚舉第 \(k\) 個
\(\Rightarrow\) 遞迴
int x = 0; int ans = 0; //已經枚舉了 cnt 項,現在要枚舉第 cnt+1 項,且這一項應該在第 lst 項之後 void solve(int lst, int cnt){ if(cnt == k){ ans = max(ans, x); //已經枚舉完整個子序列就更新答案 return; } for(int i = lst + 1; i < n; i++){ x ^= a[i]; solve(i, cnt + 1); x ^= a[i]; } }
遞迴深度:\(k\)
終止點:\(C^n_k\)
時間複雜度:\(O(kC^n_k)\)
枚舉 \(C^{n^2}_n\) 種位置組合再檢查?
顯然不行
同一行上只有恰一個皇后
同一列上也是
枚舉每一列上的皇后在哪一行
有 \(n!\) 種可能
再檢查有沒有同一條斜線上的
如果前面幾個就出現在同一條斜線上的了呢?
遞迴枚舉每一列上的皇后在哪一行
如果發現同一斜線或同一行上也有
就不要繼續枚舉下一列
遞迴枚舉時不排除用過的行
可以做到 \(n=14\)
再用一些方式避免枚舉到用過的行就好 (?)
int n, ans = 0; vector<bool> lr, rl; queue<int> c; void solve(int now){ if(now == n){ ans++; return; } int t = c.size(); while(t--){ int i = c.front(); if(lr[i - now + n] || rl[i + now]){ c.pop(); c.push(i); continue; } c.pop(); lr[i - now + n] = rl[i + now] = true; solve(now + 1); lr[i - now + n] = rl[i + now] = false; c.push(i); } } int main(){ ios_base::sync_with_stdio(false); cin.tie(0); while(cin >> n){ ans = 0; lr.resize(2 * n); rl.resize(2 * n); while(!c.empty()) c.pop(); for(int i = 0; i < n; i++) c.push(i); solve(0); cout << ans << "\n"; } return 0; }
遞迴?
當然可以
時間複雜度:\(O(2^n)\)
但有點難寫
每一個元素都有兩種狀態:
取或不取
用一個整數表示每個元素的狀態
在二進位下的第 \(i\) 個位元是 1
就表示第 \(i\) 個元素要取
否則就是不取
枚舉 \(1 \sim 2^n-1\)
再用 \(O(n)\) 時間計算 xor 和
時間複雜度:\(O(n2^n)\)
int ans = 0; for(int i = 0; i < (1 << n); i++){ int now = 0; for(int j = 0; j < n; j++){ if(i & (1 << j)) now ^= a[j]; } ans = max(ans, now); }
next_permutation(a.begin(), a.end())
prev_permutation(a.begin(), a.end())
用兩個指標做事
\(O(NQ \log N)\) 作法:
用一個 map 存每一種數字的數量
詢問時對每一個 \(a_i\) 找有沒有數字是 \(a_i-K\)
(注意 \(K=0\) 時的狀況)
\(\Rightarrow\) TLE
\(O(NQ)\) 作法:
雙指標
想聽的話我下課後再講給你聽 (?)
不斷用「目前看起來最好」的方式解決問題
有一個長度 \(n\) 的數列 \(a\)
這個數列裡取 \(k\) 個元素的和最大能是多少?
0/1 背包問題
有 \(n\) 個物品
價值和體積分別是
\(v_1, v_2, ..., v_n\) 和 \(w_1, w_2, ..., w_n\)
還有一個大小 \(m\) 的背包
所有放進背包裡的物品體積和不能超過容量
求能放進的物品的總價值最多是多少
先取 CP 值大的?
\(n=4\)、\(m=6\)
\(v=\{8,3,3,3\}\)
\(w=\{5,2,2,2\}\)
得出的答案:\(8\)
正確答案:\(9\)
先亂想一個作法
先選擇左端點在較左邊的
如果不會跟之前選過的線段重疊就選
不然就不選
\([1,10]\)
\([2,3]\)
\([4,5]\)
得出的答案:1 條(選了 \([1,10]\))
正確答案:2 條(\([2,3]\) 和 \([4,5]\))
正確作法:
先取右端點在較左邊的線段
我們得出的最佳解:\(G\)
某一個最佳解:\(G'\)
\(G\) 和 \(G'\) 中第一條不一樣的線段:\(G_i\)、\(G'_i\)
\(G\) 的大小肯定和 \(G'\) 一樣
\(G'\) 肯定可以變成 \(G\)