理由 1. 我好爛QQ
理由 2. 見下一頁
蕭梓宏 <3
希望能以經典題演示與實際解題為主
若備課不充分敬請多多包涵 ><
可是... 抄自己的不算抄吧QQ
而且我有偷改一下啦
分治法 - 將問題拆分成小問題,再一一解決
Divide:拆成相同結構的小問題
Conquer:遞迴解決小問題
Combine:利用小問題的答案解決更大的問題
求 abmodm
a,b≤1018,m≤109
Divide:
先計算 a⌊2b⌋modm 的值
Conquer:
遞迴解決小問題
Combine:
ab≡(a⌊2b⌋)2(modm)
如果 b 是奇數則答案要再乘上 a
Combine:
ab≡(a⌊2b⌋)2(modm),如果 b 是奇數則答案要再乘上 a
T(n)=T(⌊2n⌋)+O(1)
T(n)=O(logn)
#include <bits/stdc++.h> #define int long long using namespace std; int power(int a, int b, int m){ if(!b) return 1; int x = power(a, b>>1, m); if(b&1) return ((x*x)%m * a)%m; else return (x*x)%m; } int main(){ int a, b, m; cin >> a >> b >> m; cout << power(a%m, b, m) << endl; }
#include <bits/stdc++.h> #define int long long using namespace std; int power(int a, int b, int m, int ans = 1) { while(b) { if(b & 1) ans *= a, ans %= m; a *= a, a %= m, p >>= 1; } return ans; } int main() { int a, b, m; cin >> a >> b >> m; cout << power(a%m, b, m) << endl; }
你現在有三根柱子跟R個大小不一的圓環,一開始所有圓環都套在第一根柱子上,且較小的圓環在上,疊成一個塔。現在你要將所有圓環都移動到第三根柱子,每次你可以從任意柱子的最上方拿取一個圓環放到任意的另一根柱子上,但是在過程中大圓環不能疊在小圓環上,你有辦法將這個過程輸出嗎?
Divide:
拆解 - solve(x, from, to)印出將x個圓盤的塔從柱子from移到柱子to的過程
Conquer:
遞迴解決小問題
Combine:
將除了最下面的圓環塔統統移開,再將最下面的圓環移至目的地,最後再把上面的圓環塔移回去
Combine:
將除了最下面的圓環塔統統移開,再將最下面的圓環移至目的地,最後再把上面的圓環塔移回去
T(n)=2T(n−1)+O(1)
T(n)=O(2n)
#include <bits/stdc++.h> using namespace std; void move(int r, int from, int to){ if(r == 1){ cout << from << ' ' << to << endl; return; } int relay = 6 - from - to; move(r - 1, from, relay); move(1, from, to); move(r - 1, relay, to); } int main(){ int n; cin >> n; move(n, 1, 3); }
題敘略
邊界條件:
如果原序列長度是1,則不用排已有序
Divide:
將序列拆成左右兩邊
Conquer:
遞迴解決小問題 - 將左右兩邊排序
Combine:
將左右兩個排好的序列合成一個排好的序列
Combine:將左右兩個排好的序列合成一個排好的序列
5 3 6 8 7 0 9 1 2 4
3 5 6 8 7 0 1 2 4 9
0 1 2 3 4 5 6 7 8 9
Conquer:
遞迴解決小問題 - 將左右兩邊排序
Combine:
將左右兩個排好的序列合成一個排好的序列
T(n)=2T(n)+O(n)
T(n)=O(nlogn)
#include <bits/stdc++.h> using namespace std; int a[500010], temp[500010]; void merge_sort(int, int); int main(){ int n; cin >> n; for(int i=0; i<n; i++) cin >> a[i]; merge_sort(0, n); for(int i=0; i<n; i++) cout << a[i] << ' '; cout << endl; }
void merge_sort(int l, int r){ if(l+1 == r) return; int mid = l + r >> 1; merge_sort(l, mid), merge_sort(mid, r); int lptr = l, rptr = mid, ptr = l; while(lptr < mid || rptr < r){ if(lptr != mid && (rptr == r || a[lptr] < a[rptr])) temp[ptr++] = a[lptr++]; else temp[ptr++] = a[rptr++]; } for(int i=l; i<r; i++) a[i] = temp[i]; }
// sorting an array with ascending order sort(arr, arr+n); // sorting an array with custom comparison sort(arr, arr+n, cmp); // sorting a vector with ascending order sort(vec.begin(), vec.end()); // sorting a vector with custom comparison sort(vec.begin(), vec.end());
給你一個數列an,問有幾組數對(i,j)滿足i<j,ai>aj
也就是問逆序數對的個數啦
邊界條件:
如果原序列長度是1,則沒有任何逆序數對
Divide:
將序列拆成左右兩邊
Conquer:
遞迴解決小問題 - 算出左右兩個區間的逆序數對數
Combine:
將左右兩邊的答案以及跨過兩邊的答案相加
Combine:將左右兩邊的答案以及跨過兩邊的答案相加
5 3 6 8 7 0 9 1 2 4
3 5 6 8 7 0 1 2 4 9
0 1 2 3 4 5 6 7 8 9
對一個數列S來說,若S的第i項si與第j項sj符合si>sj,並且i<j的話,那麼我們說(i,j)是一個逆序數對。請問給定S,總共有多少個逆序數對呢?
給一個長度2N的數列,現在你可以對這個序列使用兩種操作:(1)將左邊2N−1個數字拿掉;(2)將右邊2N−1個數字拿掉,每次操作結束之後數列長度都會變成一半。你可以使用這兩種操作任意多次,而使得序列變成一個遞增序列。請問這個遞增序列的長度最長是多少? (N≤16)
一個有偶數層的河內塔,有a,b,c三根柱子,假設所有的環原本在a柱上,請將奇數號的環移到b柱上,偶數號的環移到c柱上,大的環不能疊在小的環上,請輸出移動過程和最少步數。
有人請你構造一個鬼腳圖出來,而他會給你一些要求,例如從左數來的第一個點要走到從左數來的第三個點、左數來的第四個點要走到從左數來的第二個點...
你的任務是輸出他給定的限制最少要幾個橫槓才能完成,讓他可以挑一個最客家的方式拿到他想要的鬼腳圖。
最多有5×105個點
給平面上N個點的座標,求距離最近的兩個點的距離到小數點第六位
有一個數列<ai>,總共有N(≤106)個整數,求數字總和最大的一段區間的總和。(區間可以為空,空區間的總和為0)
給你一個數列S,一個該數列的連續和(Continuous Sum,以下簡稱CS)是指S當中的某些連續項之總和。
很容易算得出來,一個總長度為項的數列S,其連續和(CS)共有 2n(n+1) 個。 注意,問題來囉!
請問,這 2n(n+1) 個連續和(CS)之中,第k大的是多少?
提示:做好前綴和之後對答案二分搜
則題目就轉化為:
對於所有 j>i,滿足 S[j]−S[i]>ans 的 i,j 有幾個?
是否小於k個?
display port
拆成相同結構的小問題
解決小問題
紀錄小問題的最優解
利用小問題的答案解決更大的問題
-- DP = DQ + Memoization --
動態規劃是1950年代,Richard Bellman 等人在研究多階段決策過程(Multistage Decision Process)時發明的優化問題方法。但他的上司不喜歡理論的研究,所以當他發明這方法時就取了一個跟數學無關的名字,也就是現在常聽到的 Dynamic Programming。
a1=a2=1
an=an−1+an−2 (n≤3)
#include <bits/stdc++.h> using namespace std; int fib(int n){ if(n <= 1) return n; return fib(n-1) + fib(n-2); } int main(){ int n; cin >> n; cout << fib(n) << endl; }
#include <bits/stdc++.h> using namespace std; int FIB[51]; int fib(int n){ if(n <= 1) return n; if(FIB[n]) return FIB[n]; return FIB[n] = fib(n-1) + fib(n-2); } int main(){ int n; cin >> n; cout << fib(n) << endl; }
#include <bits/stdc++.h> using namespace std; int FIB[51]; int main(){ int n; cin >> n; FIB[1] = FIB[2] = 1; for(int i=3;i<n;i++){ FIB[i] = FIB[i-1] + FIB[i-2]; } cout << FIB[n] << endl; }
#include <bits/stdc++.h> using namespace std; int main(){ int n, a, b, c; cin >> n; a = b = 1; for(int i=3;i<n;i++){ c = a + b, a = b, b = c; } cout << c << endl; }
滾動、矩陣快速冪...
決定狀態
列出轉移式
打好基底 (邊界)
決定狀態:dp[i] - 費氏數列第 i 項
列出轉移式:dp[i] ← dp[i-1] + dp[i-2]
打好基底 (邊界):dp[1] = dp[2] = 1
最優子結構:這個狀態的最優解仰賴子問題的最優解,也就是說狀態的定義時常是某狀態的最大/最小值或直接是某問題的答案。
無後效性:若求出dp[3]=2,那dp[3]永遠是2,不會因為要求不同的dp[i]而改變dp[3]的值。
重複子問題:計算dp[4]可能要用到dp[3],問題重複了,不需要重算一次dp[3]。
有一個數列<ai>,總共有N(≤107)個整數,求數字總和最大的一段區間的總和。(區間可以為空,空區間的總和為0)
1. 枚舉所有區間,逐一加起來,O(n3)
2. 前綴求區間和,O(n2)
3. 分治,O(nlogn)
感覺都會TLE,試試看O(n)DP吧!
決定狀態:
dp[i] - 以第 i 個數字結尾(或空區間)的最大區間和
列出轉移式:
dp[i] ← max(0, dp[i-1] + a[i])
打好基底 (邊界):
dp陣列初始化成0就好了
這樣一來我們最後將所有dp值取max就是答案了
#include <bits/stdc++.h> using namespace std; int a[10000010], dp[10000010]; int main(){ int n; cin >> n; for(int i=0; i<n; i++) cin >> a[i]; for(int i=0; i<n; i++) dp[i] = max(0, dp[i-1] + a[i]); cout << *max_element(dp, dp+n) << endl; }
有一個數列<ai>,總共有N(≤107)個整數,求數字總和最大的一段區間的總和。(區間不可以為空)
蕭電有N個硬幣,但是他的硬幣來自特殊國度,所以會有奇怪的面額,算起錢來也特別麻煩。假設第i個硬幣的面額是ai元,而商品的價格是p元,請問蕭電能夠在不需找零的情況下購買此商品並付清嗎?(N≤104,ai≤104)
1. 決定狀態:
dp[i][j] - 只利用前 i 個硬幣能否湊出 j 元
2. 列出轉移式:
dp[i][j] ← dp[i-1][j-a[i]] OR dp[i-1][j]
3. 打好基底 (邊界):dp[0][0] ← true
根據狀態定義,dp[N][p] 就是答案!
1. 決定狀態:
dp[i][j] - 只利用前 i 個硬幣能否湊出 j 元
重要觀念 - 空間複雜度:
i≤104,j≤104
空間複雜度約 4×108 bytes
狀態佔太多空間!
2. 列出轉移式:
dp[i][j] ← dp[i-1][j-a[i]] OR dp[i-1][j]
可以發現取得dp[i]的任何一個值只要用到dp[i-1]這條陣列。我們最後只要dp[N][p],所以在計算dp[i]時,dp[i-2]以前的數值都可以忽略!
所以我們可以重複使用陣列,這就是滾動dp。
2. 列出轉移式:
dp[i][j] ← dp[i-1][j-a[i]] OR dp[i-1][j]
我們可以重複使用陣列,這就是滾動dp,
滾動dp很簡單,直接把維度拔掉就行了。
dp[j] ← dp[j-a[i]] OR dp[j]
#include <bits/stdc++.h> using namespace std; int a[10010], dp[10010]; int main(){ int n, p; cin >> n >> p; dp[0] = 1; for(int i=1; i<=n; i++) cin >> a[i]; for(int i=1; i<=n; i++) for(int j=p; j>=0; j--) if(j-a[i] >= 0) dp[j] = max(dp[j], dp[j-a[i]]); cout << dp[p] << endl; }
蕭電有N個硬幣,假設第i個硬幣的面額是ai元、重量是wi克,他想要將總重量不超過W克的硬幣送給女朋友致瑄當生日禮物,請問蕭電最多能送給女朋友多少錢呢?(N≤104,ai≤104)
蕭電有N個硬幣,假設第i個硬幣的面額是ai元、重量是wi克,他想要將總重量不超過W克的硬幣送給女朋友致瑄當生日禮物,請問蕭電要拿哪幾個硬幣才能送給女朋友最多錢呢?(N≤103,ai≤103)
這裡有N個人排成一列,每個人的身高都不一樣,我們假設第i個人的身高是hi。現在你要在這列人中挑選一些人出來,而且你想要這些人之中越右邊的人身高要越高才行。在這樣的條件下,你最多可以挑選出多少人呢? (N≤2000,hi≤109)
決定狀態:
dp[i] - 最右邊為左邊第 i 個人的LIS長度
列出轉移式:
dp[i] ← j<i,hj<himax{dp[j]} + 1
打好基底 (邊界):
dp陣列初始化成0就好了嘍
#include <bits/stdc++.h> using namespace std; int dp[2010], h[2010]; int main(){ int n; cin >> n; for(int i=0; i<n; i++) cin >> h[i]; for(int i=0; i<n; i++){ for(int j=0; j<i; j++) if(h[j] < h[i]) dp[i] = max(dp[i], dp[j]); dp[i]++; } cout << *max_element(dp, dp+n) << endl; }
改變限制:N≤105
這題其實有O(nlogn)的解法!
讓我們改變一下DP方式吧!
換一種狀態:
dp[i][j] - 前i個數字中所有長度為 j+1 的遞增子序列最右邊數字的最小值
列出轉移式:
dp[i][j] ← j=0或dp[i−1][j−1]<a[i]min{a[i]}
打好基底 (邊界):
dp陣列初始化成INF (很大的數字)
換一種狀態:
dp[i][j] - 前i個數字中所有長度為 j+1 的遞增子序列最右邊數字的最小值
列出轉移式:
dp[i][j] ← j=0或dp[i−1][j−1]<a[i]min{dp[i-1][j], a[i]}
明顯看到 i 這個維度可以重複利用,把它拔掉
dp[j] ← j=0或dp[j−1]<a[i]min{dp[j], a[i]}
換一種狀態:
dp[j] - 前i個數字中所有長度為 j+1 的遞增子序列最右邊數字的最小值
列出轉移式:
dp[j] ← j=0或dp[j−1]<a[i]min{dp[j], a[i]}
1. 不管 i 是多少,dp[j] 隨著 j 增加而增加
2. 對於每個 i,轉移時用到 a[i] 的 j 只有一個,其餘都是從 dp[j] 轉移來
二分搜從 a[i] 轉移的 j!
#include <bits/stdc++.h> using namespce std; int a[100010], dp[100010]; int main(){ int n; cin >> n; for(int i=0; i<n; i++) cin >> a[i]; fill(dp, dp+n, 1e9); for(int i=0; i<n; i++) *lower_bound(dp, dp+n, a[i]) = a[i]; cout << lower_bound(dp, dp+n, 1e9) - dp << endl; }
這裡有N個人排成一列,每個人的身高與能力都不一樣,我們假設第i個人的身高是hi。現在你要在這列人中挑選一些人出來,而且你想要這些人之中越右邊的人身高要越高才行。在這樣的條件下,你需要挑選出哪些人呢? (N≤2000,hi≤109,ai≤109)
這裡有N個人排成一列,每個人的身高與能力都不一樣,我們假設第i個人的身高是hi、能力是ai。現在你要在這列人中挑選一些人出來,而且你想要這些人之中越右邊的人身高要越高才行。在這樣的條件下,你挑選出的這些人能力總和最大是多少呢? (N≤2000,hi≤109,ai≤109)
給一個小於13的整數n,輸出n!的值。
給一個有數字構成的三角形,現在請問從最頂端走到最底端最大的和是多少。
* 每個點只能往左下或右下走,底層的點不能再往下走。
* 三角形的高度介於1到100之間。
* 三角形上的數字都介於0到99之間。
你要用許多1×2的磚頭蓋一個2×n的磚牆,總共可以蓋出幾種花樣呢?(磚塊可以直放、橫放,磚塊不可切割且一定要鋪滿磚牆) (n≤50)
In how many ways can you tile a 3×n rectangle with 2×1 dominoes? (n≤30)
請問要將字串A改成字串B最少需要多少次操作
(|A|, |B| ≤1000)
Tips: getline(cin, s);
給你長度為N的一個正整數序列,請你求出最長嚴格遞增子序列的長度。
所謂嚴格遞增子序列,是指去掉序列中的某些數字之後,剩下的子序列是嚴格遞增的。
n個相同的箱子要放入m個不同的球,總共有幾種放法?
5≤n,m≤200
有N個俄羅斯娃娃,第i個娃娃的高度是hi、寬度是wi。如果一個娃娃的寬度與高度都小於另一個,那麼就可以將兩個娃娃嵌套。你的任務是根據這些寬度以及高度算出最多可以嵌套多少層的娃娃。(N≤20000)
教練想要挑幾個人組隊打籃球,他希望挑到的人身高總和越大越好。現在所有選手兩個兩個並排成兩排,每一排都是N個人。教練不喜歡選到連續的人,所以只要有一個人被選到,他的前後或旁邊的人都不能再選。現在輸入每個人的身高,求教練能選到的最大身高總和。(N≤105)
給一個N個數字的序列,你每次可以選擇一個不是最左邊或最右邊的數字ai把它消除,不過這樣需要花費 ai−1×ai×ai+1 的代價,直到序列只剩下左右兩個數字為止。求操作完成後的最小花費。(N≤50)
有n段路,每段路有一個分數ai,你每段路可以用其中一種速度
1.用走的:你不會得到任何分數
2.用跑的:你會得到ai的分數
3.用衝的:你會得到2ai的分數,但你下一段路得用走的
請問你最多能得到多少分? (n≤50)
火場裡有N個人(N≤100),每個人因為被困在不同地方所以有不同救援難度,假設拯救第i個人所需的時間是ti。因為火勢的蔓延,第i個人要趕在時間點di以前被救出來。由於救援隊私心的緣故,他們偷偷將每個人賦予權重pi,並且希望被拯救的那些人權重和越大越好。你能知道哪些人終會得救嗎?(ti≤20,di≤2000,pi≤20)
有一排n個方塊,每個方塊都有顏色,每次可以把連續顏色的一段消掉,若消去的方塊有L個,則可以得到L2的分數,請問全部消完最多可以得到幾分?(n≤200)
有天爸爸交代小明幫忙把寫好的信裝進信封裡,信與信封上的名字要配對。例如:給王大毛的信要裝到寫有王大毛的信封。這時頑皮的小明想到一個惡作劇,就是把所有人的信與信封都裝錯;也就是說沒有一個人會收到正確寄給自己的信。例如:A收到B的信,B收到C的信,C收到A的信。
請幫小明算算,到底有多少種裝法可以不讓任何人收到應該寄給自己的信。(最多20人)
有兩個人在N×M的格子裡找寶藏,每個格子可能是寶藏、障礙物或空地。兩個人都由左上角進,右下角出,且每次只能往右或往下。若兩人可以分開行動,且一個寶藏只能拿一次,問兩人總共最多可以拿到多少寶藏。(N,M≤100)
給你一個氣泡排序的code以及一個N個數的序列,你現在有N個點但沒有邊的圖,當氣泡排序執行中兩數每進行一次交換,就將編號是這兩數的頂點連一條邊。求排序完成後此圖的最大點獨立集大小。N≤105
有三個人(P, E, C)走在一條筆直的路徑上,路徑上依序有N顆石塊排成一排(N≤2×106),且每一顆都只能被特定其中一個人撿起。之後P、E、C會各選一個區間將區間內所有可以被他撿起的石塊都拿起來。他們所選定的區間兩兩交集必須要是空的。
說石持那十塊,P、E、C三個人已經走完這條路並且撿好石塊了。請計算他們三人所持有的石塊個數總和最大是多少。
給一個字串S,求其中一個最長的回文子序列,如果長度超過1000就只要輸出長度為1000的回文子序列即可(∣S∣≤200000)
這題時限卡很緊,空間也卡很緊
給兩個字串A, B,請輸出這兩個字串的其中一個最長公共子序列 (|A|, |B| ≤10000)
這題時限跟空間又卡更緊了
給定一張有向帶權圖,求權重和最小的漢米爾頓迴圈(Hamiltonian Cycle)
不妨設 dp[S][v] 為從
"已經訪問過S裡面所有點,且現在位於v點上"
這個狀態經過剩下所有頂點回到頂點0的最小權重和。
那麼答案就是 dp[空集合][0]
決定狀態:
對於所有不在S裡面的點u,都可以走過去
然後看看走到哪裡會比較划算
dp[S][v]=min{dp[S∪{u}][u]+cost(v,u)}
列出轉移式:
若經過所有點之後又回到0,則權重和是0:
dp[全][0]=0
剩下的狀態就用很小的數字(e.g. -1e18)等待更新
打好基底:
二進制表示法:用整數表達集合的方法
例如有
{1, 2, 5, 6} = 21+22+25+26
{0, 1, 4, 7, 6} = 20+21+24+27+26
如此一來每一個整數都對應到一個集合
二進制表示法的性質:
好用的暴力枚舉技巧 有點離題了QQ
for(int i=0;i<(1<<n);i++) { // 枚舉所有集合 }
int sub = sup; do { // 枚舉sup的所有子集 sub = (sub - 1) & sup; } while(sub != sup);
int tmp = (1 << k) - 1; while(tmp < (1 << n)) { // 枚舉大小為k的集合 int x = tmp & -tmp, y = tmp + x; tmp = ((tmp & ~y) / x >> 1) | y; }
#include<bits/stdc++.h> using namespace std; int dp[1 << 16][16]; signed main() { for(int S=0;S<(1<<n);S++) fill(dp[S], dp[S] + n, -1e18); dp[(1 << n) - 1][0] = 0; // 目標 for(int S=(1<<n)-1-1;S>=0;S--) { for(int v=0;v<n;v++) for(int u=0;u<n;u++) { if(!(S >> u & 1)) { dp[S][v] = min(dp[S][v], dp[S | 1 << u][u] + d[v][u]); } } } cout << dp[0][0] << endl; }
大家應該都看過題目了吧
如果大家都AC了就跳過吧XD
第 i 列
01001 = 9
枚舉一整列的所有開關情形相當於枚舉 0 到 2N−1 的所有整數
11010 = 26
第 i+1 列
#include<bits/stdc++.h> using namespace std; int dp[16][1 << 16], ans; signed main() { for(int msk=0;msk<(1 << n);msk++) dp[0][msk] = 1; for(int i=1;i<n;i++) { for(int msk=0;msk<(1 << n);msk++) { for(int pre=0;pre<(1 << n);pre++) { if(valid(msk, pre)) dp[i][msk] += dp[i-1][pre]; } } } for(int msk=0;msk<(1 << n);msk++) ans += dp[n-1][msk]; cout << ans << endl; }
dp[i][msk] = 只考慮第i列以上的窗戶,且第i列的開關狀態剛好是msk的方法數
如果改成一格一格做
dp[i][j][msk] = ?
沒錯就是這樣 可以發現利用計算好的所有dp[i][j][pre]很好計算dp[i][j+1][msk]
會MLE,所以至少得滾動一個維度
塞到矩陣裡
地鼠基地是一個長型的基座,基座上每隔一公尺就會有一個地鼠洞。玩家站在這個基地的最左邊,與第一個地鼠洞相距一公尺。
已知由左而右編號為i的地鼠洞每Ti秒地鼠會出現一次,且玩家移動一公尺需要一秒鐘。若被打中的地鼠便不再出現,求將所有地鼠打完所需的最少秒數。
提示:洞不多,最多16個
某校有M種不同的課程,其中有些課程的時間會有衝堂。如果給定每組有衝堂的課程,且知道學校中總共有N間不同的教室,請問共有多少種安排各課程上課教室的方式?最少要用到幾間教室?(N,M≤10)
提示:我看不懂題目
如果說一個數列s符合
s0=a
s1=b
sn=xan−2+yan−1(2≤n)
那我們就稱這個數列為索拉數列(sola sequence)
求索拉數列第n項
其中n不會大於1,000,000,000
而a,b,x,y則會小於232
螞蟻在一張由鍋子跟筷子組成的無向圖上走,且走每條邊的機率都一樣。已知螞蟻從每個鍋子當作起點的機率都一樣,求螞蟻走了T步後停在第X個鍋子上的機率是多少。
鍋子數N≤100,T≤109。
一個正方形的鎮區分為 N×N 個小方塊。農場位於方格的左上角,集市位於左下角。小優穿過小鎮,從左上角走到左下角,剛好經過每個方格一次。他想要知道有多少走法。(N≤10)
有 N×N 的農田,每塊農田種有不同價值的蘿蔔。但是相鄰八格的蘿蔔無法一起被拔起,求能夠拔起的最大價值總和。(N≤22)