主要内容:
1.状态压缩DP2.DP基础优化
3.DP单调性优化
4.拓扑排序
5.最小生成树
6.最短路及差分约束
状态压缩DP
定义:
状态压缩DP,顾名思义就是将状态压缩起来.
模板:
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 12, M = 1 << N; int n, m; LL f[N][M]; vector<int> state[M]; bool st[M]; int main() { while (cin >> n >> m, n || m) { for (int i = 0; i < 1 << n; i ++ ) { int cnt = 0; bool is_valid = true; for (int j = 0; j < n; j ++ ) if (i >> j & 1) { if (cnt & 1) { is_valid = false; break; } cnt = 0; } else cnt ++ ; if (cnt & 1) is_valid = false; st[i] = is_valid; } for (int i = 0; i < 1 << n; i ++ ) { state[i].clear(); for (int j = 0; j < 1 << n; j ++ ) if ((i & j) == 0 && st[i | j]) state[i].push_back(j); } memset(f, 0, sizeof f); f[0][0] = 1; for (int i = 1; i <= m; i ++ ) for (int j = 0; j < 1 << n; j ++ ) for (auto k : state[j]) f[i][j] += f[i - 1][k]; cout << f[m][0] << endl; } return 0; }
DP基础优化
LIS的O(nlgn)算法
模板:
#include<bits/stdc++.h> using namespace std; const int MAXN=4e4+1; int a[MAXN],b[MAXN],m,n,len; int bs(int left,int right,int a[],int val) { int r=right,l=left; while(l<r) { int mid=r+l>>1; if(val<a[mid])r=mid; else l=mid+1; } return l; } int main() { scanf("%d",&m); for(int k=0; k<m; k++) { scanf("%d",&n); for(int i=1; i<=n; i++)scanf("%d",&a[i]); b[1]=a[1]; len=1; for(int i=2; i<=n; i++) { if(a[i]>b[len]) { len++; b[len]=a[i]; } else { int index = bs(1,len,b,a[i]); b[index]=a[i]; } } printf("%d\n",len); } return 0; }
DP的滚动数组优化
模板:
#include<bits/stdc++.h> using namespace std; const int maxn=510,mod=1e9+7; char mp[510][510]; int dp[2][510][510]; int n,m; int nx[]= {0,0,-1,-1}; int ny[]= {0,1,1,0}; int main() { cin>>n>>m; for(int i=1; i<=n; i++) cin>>mp[i]+1; int now=0; if(mp[1][1]==mp[n][m]) dp[now][1][n]=1; for(int len=1; len<=(n+m-2)/2; len++) { now^=1; for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) dp[now][i][j]=0; for(int x1=1; x1-1<=len&&x1<=n; x1++) { for(int x2=n; n-x2<=len&&x2>=1; x2--) { int y1=1+len-(x1-1),y2=m-len+(n-x2); if(mp[x1][y1]==mp[x2][y2]) { for(int k=0; k<4; k++) { dp[now][x1][x2]=(dp[now][x1][x2]+dp[now^1][x1+nx[k]][x2+ny[k]])%mod; } } } } } int res=0; for(int i=1; i<=n; i++) res=(res+dp[now][i][i])%mod; if((n+m)%2) { for(int i=1; i<n; i++) res=(res+dp[now][i][i+1])%mod; } cout<<res<<endl; return 0; }
前缀不会!!!
DP单调性优化
模板:
#include<bits/stdc++.h> using namespace std; const int N = 300010, INF = 1e9; int n, m; int s[N]; int q[N]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]), s[i] += s[i - 1]; int res = -INF; int hh = 0, tt = 0; for (int i = 1; i <= n; i ++ ) { if (q[hh] < i - m) hh ++ ; res = max(res, s[i] - s[q[hh]]); while (hh <= tt && s[q[tt]] >= s[i]) tt -- ; q[ ++ tt] = i; } printf("%d\n", res); return 0; }
DP斜率优化
模板:
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 5010; int n, s; int sc[N], st[N]; LL f[N]; int main() { scanf("%d%d", &n, &s); for (int i = 1; i <= n; i ++ ) { scanf("%d%d", &st[i], &sc[i]); st[i] += st[i - 1]; sc[i] += sc[i - 1]; } memset(f, 0x3f, sizeof f); f[0] = 0; for (int i = 1; i <= n; i ++ ) for (int j = 0; j < i; j ++ ) f[i] = min(f[i], f[j] + (sc[i] - sc[j]) * (LL)st[i] + (LL)s * (sc[n] - sc[j])); printf("%lld\n", f[n]); return 0; }
拓扑排序
模板:
#include<bits/stdc++.h> using namespace std; const int N = 100010; int n, m; int h[N], e[N], ne[N], idx; int d[N]; int q[N]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } bool topsort() { int hh = 0, tt = -1; for (int i = 1; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (-- d[j] == 0) q[ ++ tt] = j; } } return tt == n - 1; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } if (!topsort()) puts("-1"); else { for (int i = 0; i < n; i ++ ) printf("%d ", q[i]); puts(""); } return 0; }
最小生成树
Kruskal算法求最小生成树
code
#include<bits/stdc++.h> using namespace std; const int N = 100010, M = 200010, INF = 0x3f3f3f3f; int n, m; int p[N]; struct Edge { int a, b, w; bool operator< (const Edge &W)const { return w < W.w; } } edges[M]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int kruskal() { sort(edges, edges + m); for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集 int res = 0, cnt = 0; for (int i = 0; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find(a), b = find(b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1) return INF; return res; } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < m; i ++ ) { int a, b, w; scanf("%d%d%d", &a, &b, &w); edges[i] = {a, b, w}; } int t = kruskal(); if (t == INF) puts("impossible"); else printf("%d\n", t); return 0; }
最短路及差分约束
提到最短路,我们先来回顾一下之前水过的最短路模板
Dijkstra
#include<bits/stdc++.h> using namespace std; const int N = 510; int n, m; int g[N][N]; int dist[N]; bool st[N]; int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n - 1; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], dist[t] + g[t][j]); st[t] = true; } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(g, 0x3f, sizeof g); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); g[a][b] = min(g[a][b], c); } printf("%d\n", dijkstra()); return 0; }
Bellman-ford
#include<bits/stdc++.h> using namespace std; const int N = 510, M = 10010; struct Edge { int a, b, c; } edges[M]; int n, m, k; int dist[N]; int last[N]; void bellman_ford() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < k; i ++ ) { memcpy(last, dist, sizeof dist); for (int j = 0; j < m; j ++ ) { auto e = edges[j]; dist[e.b] = min(dist[e.b], last[e.a] + e.c); } } } int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < m; i ++ ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); edges[i] = {a, b, c}; } bellman_ford(); if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); else printf("%d\n", dist[n]); return 0; }
Spfa
#include<bits/stdc++.h> using namespace std; const int N = 100010; int n, m; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } int spfa() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue<int> q; q.push(1); st[1] = true; while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push(j); st[j] = true; } } } } return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int t = spfa(); if (t == 0x3f3f3f3f) puts("impossible"); else printf("%d\n", t); return 0; }
Floyd
#include<bits/stdc++.h> using namespace std; const int N = 210, INF = 1e9; int n, m, Q; int d[N][N]; void floyd() { for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); } int main() { scanf("%d%d%d", &n, &m, &Q); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) if (i == j) d[i][j] = 0; else d[i][j] = INF; while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); d[a][b] = min(d[a][b], c); } floyd(); while (Q -- ) { int a, b; scanf("%d%d", &a, &b); int t = d[a][b]; if (t > INF / 2) puts("impossible"); else printf("%d\n", t); } return 0; }
tips:后两种非必要情况下不要用,不然有可能这样
这样
或是这样
不要问我是怎么知道的……
最后剩下差分约束,来道例题看一看
幼儿园里有 NN 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 KK 个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N,KN,K。
接下来 KK 行,表示分配糖果时需要满足的关系,每行 33 个数字 X,A,BX,A,B。
- 如果 X=1X=1.表示第 AA 个小朋友分到的糖果必须和第 BB 个小朋友分到的糖果一样多。
- 如果 X=2X=2,表示第 AA 个小朋友分到的糖果必须少于第 BB 个小朋友分到的糖果。
- 如果 X=3X=3,表示第 AA 个小朋友分到的糖果必须不少于第 BB 个小朋友分到的糖果。
- 如果 X=4X=4,表示第 AA 个小朋友分到的糖果必须多于第 BB 个小朋友分到的糖果。
- 如果 X=5X=5,表示第 AA 个小朋友分到的糖果必须不多于第 BB 个小朋友分到的糖果。
小朋友编号从 11 到 NN。
输出格式
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1−1。
数据范围
1≤N<1051≤N<105,
1≤K≤1051≤K≤105,
1≤X≤51≤X≤5,
1≤A,B≤N1≤A,B≤N,
输入数据完全随机。
输入样例:
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
输出样例:
11
代码:
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 100010, M = 300010; int n, m; int h[N], e[M], w[M], ne[M], idx; LL dist[N]; int q[N], cnt[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; } bool spfa() { int hh = 0, tt = 1; memset(dist, -0x3f, sizeof dist); dist[0] = 0; q[0] = 0; st[0] = true; while (hh != tt) { int t = q[ -- tt]; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] < dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n + 1) return false; if (!st[j]) { q[tt ++ ] = j; st[j] = true; } } } } return true; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int x, a, b; scanf("%d%d%d", &x, &a, &b); if (x == 1) add(b, a, 0), add(a, b, 0); else if (x == 2) add(a, b, 1); else if (x == 3) add(b, a, 0); else if (x == 4) add(b, a, 1); else add(a, b, 0); } for (int i = 1; i <= n; i ++ ) add(0, i, 1); if (!spfa()) puts("-1"); else { LL res = 0; for (int i = 1; i <= n; i ++ ) res += dist[i]; printf("%lld\n", res); } return 0; }
溜了溜了……