本文最后更新于:几秒前
基础算法 快排 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void quicksort (int num[],int i,int j) { if (i >= j) return ; int l = i-1 ,r = j+1 ,m=num[(l+r)/2 ]; while (l < r){ do l++; while (num[l]<m); do r--; while (num[r]>m); if (l < r) { swap (num[l], num[r]); } } quicksort (num,i,r); quicksort (num,r+1 ,j); }
归并 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void mergesort (int num[],int l,int r) { if (l >= r)return ; int m = (l+r)/2 ,tp[r-l+1 ]; mergesort (num,l,m); mergesort (num,m+1 ,r); int i = l,j = m+1 ,k = 0 ; while (i <= m && j <= r){ if (num[i] < num[j]){ tp[k] = num[i];i++;k++; } else { tp[k] = num[j];j++;k++; } } while (i <= m){ tp[k] = num[i];i++;k++; } while (j <= r){ tp[k] = num[j];j++;k++; } for (int i = 0 ;i < k;i++){ num[l+i] = tp[i]; } }
逆序数思想: res = mergesort(l,m) + mergesort(m+1,r)+[当num[i]>num[j]时,i到m的m-i+1个数均为j指向数的逆序数]
二分法 本质:对边界情况的讨论,如下图
模板:
1、确定二分中间值 m=(l+r)/2 或 m=(l+r+1)/2;
2、确定check函数
3、判断,移动端点位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool check (int m,int q) { return num[m] < q?true :false ; }bool valid (int l,int q) { return num[l+1 ] >= q?true :false ; }int idx (int l,int r,int q) { while (l < r){ m = l+r+1 >> 1 ; if (check (m,q)) l = m; else r = m-1 ; } if (valid (l,q)) return l; else return -1 ; }
二分模板一共有两个,分别适用于不同情况。 算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
版本1 当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。
C++ 代码模板:
1 2 3 4 5 6 7 8 9 10 int bsearch_1 (int l, int r) { while (l < r) { int mid = l + r >> 1 ; if (check (mid)) r = mid; else l = mid + 1 ; } return l; }
版本2 当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。
C++ 代码模板:
1 2 3 4 5 6 7 8 9 10 int bsearch_2 (int l, int r) { while (l < r) { int mid = l + r + 1 >> 1 ; if (check (mid)) l = mid; else r = mid - 1 ; } return l; }
浮点数二分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std;int main () { double n;cin >> n; double l = -10000 ,r = 10000 ,m; while (r-l >= 1e-8 ){ m = (l+r)/2 ; if (m*m*m >= n) r = m; else l = m; } printf ("%.6f" ,m); return 0 ; }
高精度算数(记成所有方法都要去除前导零) 加法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <vector> #include <cstring> #include <cstdlib> using namespace std;const int N = 1e5 +10 ;vector<int > add (vector<int > va,vector<int > vb) { int c=0 ,i=0 ;vector<int >res; while (i < va.size () || i < vb.size ()){ if (i < va.size ()) c+=va[i]; if (i < vb.size ()) c+=vb[i]; res.push_back (c%10 ); c /= 10 ;i++; } if (c)res.push_back (c); while (res.size ()>0 && res.back ()==0 ) res.pop_back (); return res; }int main () { char a[N],b[N]; scanf ("%s%s" ,a,b); vector<int > va,vb; for (int i = strlen (a)-1 ;i >= 0 ;i--)va.push_back (a[i]-'0' ); for (int i = strlen (b)-1 ;i >= 0 ;i--)vb.push_back (b[i]-'0' ); vector<int > res = add (va,vb); for (int i = res.size ()-1 ;i >= 0 ;i--)cout << res[i]; return 0 ; }
减法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std;const int N = 1e5 +10 ;bool upper (vector<int > va,vector<int > vb) { if (va.size () != vb.size ()) return va.size () > vb.size (); else { for (int i = va.size ()-1 ;i >= 0 ;i--){ if (va[i] != vb[i])return va[i]>vb[i]; } } return true ; }vector<int > minus_ (vector<int > va,vector<int > vb) { vector<int > res; int t=0 ,i=0 ; while (i < va.size ()){ t=va[i]-t; if (i<vb.size ())t-=vb[i]; res.push_back ((t+10 )%10 ); t<0 ?t=1 :t=0 ;i++; } while (res.size ()>1 &&res.back ()==0 )res.pop_back (); return res; }int main () { char a[N],b[N];scanf ("%s%s" ,a,b); vector<int > va,vb; for (int i = strlen (a)-1 ;i >= 0 ;i--) va.push_back (a[i]-'0' ); for (int i = strlen (b)-1 ;i >= 0 ;i--) vb.push_back (b[i]-'0' ); vector<int > res; if (upper (va,vb))res = minus_ (va,vb); else res = minus_ (vb,va); if (!upper (va,vb))cout << "-" ; for (int i = res.size ()-1 ;i >= 0 ;i--)cout << res[i]; return 0 ; }
乘法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<int > mul (vector<int > a,int b) { vector<int > res; int c=0 ,i=0 ; while (i < a.size ()){ res.push_back ((a[i]*b+c)%10 ); c = (a[i]*b+c)/10 ;i++; } if (c)res.push_back (c); while (res.size ()>1 && res.back ()==0 )res.pop_back (); return res; }
除法(注意除法是正序处理,不用从个位开始倒序处理) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <algorithm> using namespace std;const int N = 1e5 +10 ;int r = 0 ;vector<int > divide (vector<int > a,int b) { vector<int > res; int i = 0 ; while (i < a.size ()){ res.push_back ((r*10 +a[i])/b); r = (r*10 +a[i])%b;i++; } reverse (res.begin (),res.end ()); while (res.size ()>1 &&res.back ()==0 )res.pop_back (); return res; }
前缀和 用于计算某个序列中一段子序列的和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <cstring> #include <cstdlib> using namespace std;const int N = 1e5 +10 ;int s[N],a[N];int main () { int n,m; cin >> n >> m; for (int i = 1 ;i <= n;i++){ cin >> a[i]; s[i] = a[i]+s[i-1 ]; } while (m--){ int l,r;cin >> l >> r; cout << s[r]-s[l-1 ] << endl; } return 0 ; }
二维前缀和:
s[i,j] = s[i,j-1]+s[i-1,j] - s[i,j]+a[i,j];
求解[x1,y1] -> [x2,y2] :s[x2,y2]-s[x1-1,y2]-s[x2,y1-1]+s[x1-1,y1-1] //注意x1-1,y1-1
差分 构造一个数组b,使当前数组a是b的前缀和数组,我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组 。
给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c ,,,,,, a[r] + c;
始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。
首先让差分b数组中的 b[l] + c ,a数组变成 **a[l] + c ,a[l+1] + c,,,,,, a[n] + c;**,对a[i]及之后的元素产生影响
然后我们打个补丁,b[r+1] - c, a数组变成 a[r+1] - c,a[r+2] - c,,,,,,,a[n] - c;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <cstdlib> using namespace std;const int N = 1e5 +10 ;int a[N],b[N];int main () { int n,m; cin >> n >> m;a[0 ]=0 ; for (int i = 1 ;i <= n;i++){ cin >> a[i]; b[i] = a[i]-a[i-1 ]; } while (m--){ int l,r,c; cin >> l >> r >> c; b[l]+=c;b[r+1 ]-=c; } for (int i = 1 ;i <= n;i++){ a[i] = a[i-1 ]+b[i]; cout << a[i] << " " ; } return 0 ; }
双指针算法 板子: 1 2 3 4 for (int i = 0 ,j = 0 ;i < n;i++){ while (j<i && check (i,j))j++; }
不重复连续子序列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;int c[N],f[N];int main () { int n,r=0 ;cin >> n; for (int i = 0 ;i < n;i++)cin >> f[i]; for (int i = 0 ,j = 0 ;i < n;i++){ c[f[i]]++; while (c[f[i]]>1 && j<n){ c[f[j]]--;j++; } r = max (r,i-j+1 ); } cout << r; return 0 ; }
数组元素目标和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;int c[N],f[N];int main () { int n,m,x;cin >> n >> m>>x; for (int i = 0 ;i < n;i++)cin >> c[i]; for (int i = 0 ;i < m;i++)cin >> f[i]; for (int i = 0 ,j = m-1 ;i < n;i++){ while (c[i]+f[j] > x)j--; if (c[i]+f[j] == x){ cout << i<< " " << j; break ; } } return 0 ; }
判断子序列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;int c[N],f[N];int main () { int n,m;cin >> n >> m; for (int i = 0 ;i < n;i++)cin >> c[i]; for (int i = 0 ;i < m;i++)cin >> f[i]; int cnt = 0 ; for (int i = 0 ,j = 0 ;i < n;i++){ while (c[i]!=f[j]&&j<m){ j++; } if (j < m){ cnt++;j++; } } if (cnt==n)cout << "Yes" ; else cout << "No" ; return 0 ; }
位运算 二进制中1的个数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std;int main () { int n;cin >> n; for (int i = 0 ;i < n;i++){ int tp,cnt=0 ;cin >> tp; while (tp){ if (tp & 1 )cnt++; tp >>= 1 ; } cout << cnt <<" " ; } return 0 ; }
离散化 特指整数、有序的离散化
特点:值域很大,但数量很少,因此可以将数值从离散映射 到连续,
注意:
①原数组中可能有重复值 ——> 去重
②如何算出x映射后的值 ——>因为原数组有序,所以可以二分
过程:
1、排序,模拟数轴
2、去重 all.erase(unique(all.begin(),all.end()),all.end());
1 2 3 sort (all.begin (),all.end ()); all.erase (unique (all.begin (),all.end ()),all.end ());
3、对原位置的操作换成对二分之后的位置的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> #include <cstring> #include <cstdlib> #include <algorithm> #include <vector> using namespace std;typedef pair<int ,int > PII;const int N = 100010 ; vector<int > alls; vector<PII> add,query;int a[N],s[N];int find (int q) { int l = 0 , r = alls.size ()-1 ; while (l < r){ int m = (l+r)/2 ; if (alls[m] >= q) r = m; else l = m+1 ; } return r; }int main () { int n,m;cin >> n >> m; for (int i = 0 ;i < n;i++){ int x,c;cin >> x >> c; alls.push_back (x); add.push_back ({x,c}); } for (int i = 0 ;i < m;i++){ int l,r;cin >> l >> r; alls.push_back (l); alls.push_back (r); query.push_back ({l,r}); } sort (alls.begin (),alls.end ()); alls.erase (unique (alls.begin (),alls.end ()),alls.end ()); for (int i = 0 ;i < n;i++){ int x = find (add[i].first); a[x] += add[i].second; } int s[N];s[0 ] = a[0 ]; for (int i = 1 ;i < alls.size ();i++){ s[i] = a[i]+s[i-1 ]; } for (int i = 0 ;i < m;i++){ int l = find (query[i].first); int r = find (query[i].second); if (!l) cout << s[r] << endl; else cout << s[r]-s[l-1 ] << endl; } return 0 ; }
区间合并 左端点排序,判断两个区间之间能够存在的三种位置关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std;const int N = 100010 ,M = -1e9 ;typedef pair<int ,int > PII; vector<PII> prd;bool cmp (const PII &a,const PII &b) { return a.first <= b.first; }int main () { int n;cin >> n; for (int i = 0 ;i < n;i++){ int l,r; cin >> l >> r; prd.push_back ({l,r}); } sort (prd.begin (),prd.end (),cmp); int i=1 ,st=prd[0 ].first,ed=prd[0 ].second,cnt=0 ; while (i < n){ int l = prd[i].first,r = prd[i].second;i++; if (l <= ed && r <= ed)continue ; else if (l <= ed && r > ed){ ed=r;continue ; } else { cnt++;st=l;ed=r; } } cout << cnt; return 0 ; }
链表 单链表板子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void init () { head=-1 ;idx=1 ; }void add_to_head (int x) { e[idx]=x;ne[idx]=head;head=idx;idx++; }void add (int k,int x) { e[idx]=x;ne[idx]=ne[k];ne[k]=idx;idx++; }void remove (int k) { ne[k] = ne[ne[k]]; }
双链表板子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void init () { r[0 ] = 1 ; l[1 ] = 0 ; idx = 2 ; }void add (int k,int x) { e[idx] = x; r[idx] = r[k]; l[idx] = k; r[k] = idx; l[r[idx]] = idx; idx++; }void remove (int k) { r[l[k]] = r[k]; l[r[k]] = l[k]; }
表达式求值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <cstring> #include <algorithm> #include <stack> #include <unordered_map> using namespace std;typedef pair<int ,int > PII;const int N = 100010 ; stack<char > d; stack<int > r;void eval () { char p = d.top ();d.pop (); int b = r.top ();r.pop (); int a = r.top ();r.pop (); switch (p) { case '+' : r.push (a+b);break ; case '-' : r.push (a-b);break ; case '*' : r.push (a*b);break ; case '/' : r.push (a/b);break ; } }int main () { unordered_map<char ,int > m = {{'+' ,1 },{'-' ,1 },{'*' ,2 },{'/' ,2 }}; char s[N];scanf ("%s" ,s); for (int i = 0 ;i < strlen (s);i++){ if (isdigit (s[i])){ int num = 0 ; while (isdigit (s[i])){ num = num*10 +s[i]-'0' ;i++; } i--;r.push (num); } else if (s[i] == '(' ) d.push (s[i]); else if (s[i] == ')' ){ while (d.top ()!='(' ) eval (); d.pop (); } else { while (d.size () && m[d.top ()] >= m[s[i]])eval (); d.push (s[i]); } } while (d.size ()) eval (); cout << r.top (); return 0 ; }
单调栈:左边最小的数 给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
思路:保证栈内的元素保序单调上升。
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int n;cin >> n; stack<int > s; while (n--){ int t;cin >> t; while (!s.empty () && s.top ()>=t)s.pop (); if (s.empty ())cout << "-1" << " " ; else cout << s.top () << " " ; s.push (t); } return 0 ; }
单调队列:滑动窗口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int n,k;int num[N],q[N],fr=0 ,rr=-1 ;void init () { fr = rr = 0 ; q[fr] = 0 ; }bool empty () { return fr == rr; }int main () { cin >> n >> k; for (int i = 0 ;i < n;i++){ cin >> num[i]; } init (); for (int i = 0 ;i < n;i++){ if (!empty () && q[fr]<i-k+1 )fr++; while (!empty () && num[q[rr-1 ]] > num[i]) rr--; q[rr]=i;rr++; if (i >= k-1 ) cout << num[q[fr]] << " " ; } init ();cout<<endl; for (int i = 0 ;i < n;i++){ if (!empty () && q[fr]<i-k+1 )fr++; while (!empty () && num[q[rr-1 ]] < num[i])rr--; q[rr]=i;rr++; if (i >= k-1 ) cout << num[q[fr]] << " " ; } return 0 ; }
并查集 合并集合 1、首先将每个数做成一个集合
2、合并的时候将p[a] = b;
三种操作:注意这里找父节点用的是find函数 1、集合合并:p[find[a]] = find[b] 2、判断是否属于同一个集合:find[a] == find[b] 3、找到父节点:p[a] = find(p[a])
并查集操作:
1 2 3 4 5 int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; }
DFS 排列数字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <cstdlib> #include <algorithm> using namespace std;const int N = 10 ;int st[N],p[N],n;void DFS (int cnt) { if (cnt > n){ for (int i = 1 ;i <= n;i++){ cout << p[i] << " " ; } cout << endl; } for (int i = 1 ;i <= n;i++){ if (!st[i]){ st[i]=1 ;p[cnt]=i; DFS (cnt+1 ); st[i]=0 ;p[cnt]=0 ; } } }int main () { cin >> n; DFS (1 ); return 0 ; }
八皇后 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int c[N],d[M],u[M],n;char s[N][N];void dfs (int cnt) { if (cnt > n){ for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= n;j++){ cout << s[i][j]; } cout << endl; } cout << endl; return ; } for (int i = 1 ;i <= n;i++){ if (!c[i] && !d[cnt-i+n] && !u[i+cnt]){ s[cnt][i] = 'Q' ; c[i] = d[cnt-i+n] = u[i+cnt] = 1 ; dfs (cnt+1 ); s[cnt][i] = '.' ; c[i] = d[cnt-i+n] = u[i+cnt] = 0 ; } } }int main () { cin >> n; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= n;j++){ s[i][j] = '.' ; } } dfs (1 ); return 0 ; }
BFS 走迷宫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <cstdlib> #include <algorithm> #include <queue> #include <cstring> using namespace std;const int N = 110 ;typedef pair<int ,int > PII; queue<PII> q;int n,m,sum=0 ;int dx[4 ] = {0 ,1 ,0 ,-1 },dy[4 ] = {1 ,0 ,-1 ,0 },loc[N][N],d[N][N];void bfs () { memset (d,-1 ,sizeof d); d[1 ][1 ]=0 ; while (!q.empty ()) { PII p = q.front (); q.pop (); int x = p.first, y = p.second; if (x == n && y == m) { cout << d[x][y]; return ; } for (int i = 0 ; i < 4 ; i++) { int xx = x + dx[i], yy = y + dy[i]; if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && !loc[xx][yy] && d[xx][yy]==-1 ) { q.push ({xx, yy});d[xx][yy]=d[x][y]+1 ; } } } }int main () { cin >> n >> m; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= m;j++){ cin >> loc[i][j]; } } q.push ({1 ,1 }); bfs (); return 0 ; }
01背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <algorithm> #include <cstdlib> using namespace std;const int N = 1010 ;int v[N],w[N],f[N];int main () { int n,m;cin >> n >>m; for (int i = 1 ;i <= n;i++){ cin >> v[i] >> w[i]; } for (int i = 1 ;i <= n;i++){ for (int j = m;j >= v[i];j--){ f[j] = max (f[j],f[j-v[i]]+w[i]); } } cout << f[m] << endl; return 0 ; }
完全背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;const int N = 1010 ;int v[N],w[N],f[N];int main () { int n,m; cin >> n >> m; for (int i = 1 ;i <= n;i++){ int a,b; cin >> a >> b; v[i] = a; w[i] = b; } for (int i = 1 ;i <= n;i++){ for (int j = v[i];j <= m;j++){ f[j] = max (f[j],f[j-v[i]]+w[i]); } } cout << f[m] << endl; return 0 ; }
多重背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <algorithm> #include <cstdlib> using namespace std;const int N = 1010 ;int v[N],w[N],s[N],f[N];int main () { int n,m;cin >> n >>m; for (int i = 1 ;i <= n;i++){ cin >> v[i] >> w[i] >> s[i]; } for (int i = 1 ;i <= n;i++){ for (int j = m;j >= v[i];j--){ for (int k = 1 ;k <= s[i];k++){ if (j >= k*v[i]) f[j] = max (f[j],f[j-k*v[i]]+k*w[i]); } } } cout << f[m] << endl; return 0 ; }
分组背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;const int N = 110 ;int f[N];int v[N][N],w[N][N],s[N];int main () { int n,m; cin >> n >> m; for (int i = 0 ;i < n;i++){ cin >> s[i]; for (int j = 0 ;j < s[i];j++){ cin >> v[i][j] >> w[i][j]; } } for (int i = 0 ;i < n;i++){ for (int j = m;j >= 1 ;j--){ for (int k = 0 ;k < s[i];k++){ if (j >= v[i][k])f[j] = max (f[j],f[j-v[i][k]]+w[i][k]); } } } cout << f[m] << endl; return 0 ; }
线性DP 数字金字塔 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;const int N = 510 ;int f[N][N];int main () { int n; cin >> n; int cnt=0 ; while (cnt++ < n){ for (int i = 1 ;i <= cnt;i++){ cin >> f[cnt][i]; } } for (int i = n-1 ;i > 0 ;i--){ for (int j = 1 ;j <= i;j++){ f[i][j] = max (f[i+1 ][j],f[i+1 ][j+1 ])+f[i][j]; } } cout << f[1 ][1 ]; return 0 ; }
上升子序列(优化到O(nlogn)) O(n^2)做法: 第n个元素所在的上升子序列的最大值取决于倒数第二个元素是什么,找到倒数第二个元素所对应的最长子序列,然后+1即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const int N = 100010 ;int a[N],f[N];int main () { int n; cin >> n; for (int i = 1 ;i <= n;i++)cin >> a[i]; int len = 0 ; f[0 ] = -2e9 ; for (int i = 1 ;i <= n;i++){ int l = 0 ,r = len; while (l < r){ int mid = (l+r+1 ) >> 1 ; if (f[mid] < a[i]) l = mid; else r = mid-1 ; } len = max (len,r+1 ); f[r+1 ] = a[i]; } cout << len; return 0 ; }
最长公共子序列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 int f[N][N];int main () { int n,m; char a[N],b[N]; cin >> n >> m >> a+1 >> b+1 ; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= m;j++){ f[i][j] = max (f[i-1 ][j],f[i][j-1 ]); if (a[i]==b[j]) f[i][j] = max (f[i][j],f[i-1 ][j-1 ]+1 ); } } cout << f[n][m]; return 0 ; }
最短编辑距离 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int main () { int n,m; char a[N],b[N]; cin >> n >> a+1 >> m >> b+1 ; for (int i = 0 ;i <= m;i++)f[0 ][i] = i; for (int i = 0 ;i <= n;i++)f[i][0 ] = i; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= m;j++){ f[i][j] = min (f[i-1 ][j]+1 ,f[i][j-1 ]+1 ); if (a[i] == b[j]) f[i][j] = min (f[i][j],f[i-1 ][j-1 ]); else f[i][j] = min (f[i][j],f[i-1 ][j-1 ]+1 ); } } cout << f[n][m]; return 0 ; }
区间DP 石子合并 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int n;int s[N],a[N],f[N][N];int main () { cin >> n; for (int i = 1 ;i <= n;i++)cin >> a[i]; for (int i = 1 ;i <= n;i++)s[i] = a[i]+s[i-1 ]; for (int len = 2 ;len <= n;len++){ for (int i = 1 ;i + len-1 <= n;i++){ int j = i+len-1 ; f[i][j] = 1e7 ; for (int k = i;k <= j-1 ;k++){ f[i][j] = min (f[i][j],f[i][k]+f[k+1 ][j]); } f[i][j] += s[j]-s[i-1 ]; } } cout << f[1 ][n]; return 0 ; }
计数类DP 整数划分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;const int N = 1010 ,mod = 1e9 +7 ;int f[N][N];int n;int main () { cin >> n; f[0 ][0 ] = 1 ; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= i;j++){ f[i][j] = (f[i-1 ][j-1 ]+f[i-j][j])%mod; } } int r = 0 ; for (int i = 1 ;i <= n;i++) r = (r+f[n][i]) % mod; cout << r; }
状态DP 哈密特路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const int N = 21 ,M = 1 << 20 ;int n;int f[M][N],w[N][N];int main () { cin >> n; for (int i = 0 ;i < n;i++){ for (int j = 0 ;j < n;j++){ cin >> w[i][j]; } } memset (f,0x3f ,sizeof f); f[1 ][0 ] = 0 ; for (int i = 0 ;i < 1 <<n;i++){ for (int j = 0 ;j < n;j++){ if (i >> j & 1 ){ for (int k = 0 ;k < n;k++){ if (i ^ (1 << j) & k){ f[i][j] = min (f[i][j],f[i ^ (1 << j)][k]+w[k][j]); } } } } } cout << f[(1 <<n)-1 ][n-1 ]; return 0 ; }
树形DP 没有上司的酒会 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 6010 ;int h[N],e[N],ne[N],hp[N],up[N],n,idx;int f[N][2 ];void add (int a,int b) { e[idx] = b;ne[idx]=h[a];h[a]=idx++; }void dfs (int n) { f[n][1 ] = hp[n]; for (int i = h[n];i != -1 ;i = ne[i]){ int d = e[i];dfs (d); f[n][0 ] += max (f[d][0 ],f[d][1 ]); f[n][1 ] += f[d][0 ]; } }int main () { cin >> n;memset (h,-1 ,sizeof h); for (int i = 1 ;i <= n;i++){ cin >> hp[i]; } for (int i = 1 ;i <= n-1 ;i++){ int a,b;cin >> a >> b; up[a]=1 ;add (b,a); } int root=1 ; while (up[root]) root++; dfs (root); cout << max (f[root][0 ],f[root][1 ]); return 0 ; }
记忆化路径 滑雪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <cstring> using namespace std;const int N = 310 ;int n,m;int f[N][N];int h[N][N];int dx[4 ] = {0 ,1 ,0 ,-1 };int dy[4 ] = {1 ,0 ,-1 ,0 };int dp (int x,int y) { if (f[x][y]) return f[x][y]; f[x][y]=1 ; for (int i=0 ;i<4 ;i++) { int xx=x+dx[i]; int yy=y+dy[i]; if (xx>=1 &&xx<=n&&yy>=1 &&yy<=m&&h[x][y]>h[xx][yy]) { f[x][y]=max (f[x][y],dp (xx,yy)+1 ); } } return f[x][y]; }int main () { cin >> n >> m; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= m;j++){ cin >> h[i][j]; } } int r = 0 ; for (int i = 1 ;i <= n;i++){ for (int j = 1 ;j <= m;j++){ r = max (r,dp (i,j)); } } cout << r; return 0 ; }
区间问题 区间选点/最大不相交区间数量 右排序->判断左端点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;typedef pair<int ,int > PII; vector<PII> v;bool cmp (PII a,PII b) { return a.second <= b.second; }int main () { int n;cin >> n; for (int i =0 ;i < n;i++){ int l,r;cin >> l >> r; v.push_back ({l,r}); } sort (v.begin (),v.end (),cmp); int r = -2e9 ,sum = 0 ; for (int i = 0 ;i < n;i++){ int l = v[i].first; if (r < l){ sum++;r = v[i].second; } } cout << sum; return 0 ; }
区间分组 左端点排序->小根堆存储每个分组的最右端点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <vector> using namespace std;const int N = 100010 ;typedef pair<int ,int > PII; vector<PII> v;bool cmp (PII a,PII b) { return a.first <= b.first; }int main () { int n;cin >> n; for (int i = 0 ;i < n;i++){ int l,r;cin >> l >> r; v.push_back ({l,r}); } sort (v.begin (),v.end (),cmp); priority_queue<int ,vector<int >,greater<int >> q; int sum; for (int i = 0 ;i < n;i++){ int l = v[i].first,r = v[i].second; if (q.empty () || q.top () >= l){ sum++;q.push (r); } else { q.pop ();q.push (r); } } cout << sum; return 0 ; }
区间覆盖 左端点排序->寻找最远的右端点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <vector> using namespace std;const int N = 100010 ;typedef pair<int ,int > PII; vector<PII> v;bool cmp (PII a,PII b) { return a.first <= b.first; }int main () { int s,t;cin >> s >> t; int n;cin >> n; for (int i = 0 ;i < n;i++){ int l,r;cin >> l >> r; v.push_back ({l,r}); } sort (v.begin (),v.end (),cmp); int ss = -2e9 ;int sum = 0 ;bool c = false ; for (int i = 0 ;i < n;i++){ int r=-2e9 ,j=i; for (;j < n;j++){ if (v[j].first <= s){ r = max (r,v[j].second); } else break ; } if (r < s)break ;sum++; if (r >= t){ c=true ;break ; } s=r;i=j-1 ; } if (c)cout << sum; else cout << "-1" ; return 0 ; }
总结 区间选点问题:右排序 ,优先选择最右点
区间分组问题:左排序 ,如果最小分组的max_r都大于当前区间的l,则代表区间与所有分组有冲突,新开一组
区间覆盖问题:左排序 ,如果l<s,则找到其符合条件区间的最右端点并更新成s,注意判断断点和结束点
绝对值不等式 货仓选址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <algorithm> using namespace std;int n;const int N = 100010 ;int f[N];int main () { cin >> n; for (int i = 0 ;i < n;i++)cin >> f[i]; sort (f,f+n); int r = 0 ; int m = n/2 ; for (int i = 0 ;i < n;i++){ r += abs (f[i]-f[m]); } cout << r; return 0 ; }