此篇收集各类DP题。
《1D1D动态规划优化初步》的3个模型
1. f[x] = min(f[i]+w[i, x]), i < x且w[i, x]满足单调性(即w[i, j]+w[i+1, j+1] <= w[i, j+1]+w[i+1, j],下四边形同)
2. f[x] = min(g[i])+w[x], b[x] <= i < x, b[x]单调递增
3. f[i]=max{x[j]*a[i]+y[j]*b[i]} = b[i]*max{a[i]/b[i]*x[j]+y[j]},变成了斜率优化
一. 四边形不等式
eg. Hiho1621:超市规划
dp[i, j] = min{ dp[k, j-1] + w[k+1, i] }, 把长度为i的序列切分成j段,可糙猛快分治
dp[i, j] = min{ dp[i, k-1] + dp[k, j] + w[i, j] }, 合并[i, j]区间。注意决策点应该在哪些范围之内单调,比如对于区间dp的方程来说,决策点的单调范围就应该是行号小于等于列号的那一部分。
以上形式通常情况下w会满足四边形不等式,会使得决策opt[i, j] 与 opt[i, j-1], opt[i-1, j]满足单调性。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 2010; 4 const double inf = 1e17; 5 double dp[N][N], c[N][N]; 6 double a[N], sum1[N], sum2[N], opt[N][N]; 7 int main() 8 { 9 int n, m; 10 scanf("%d%d",&n,&m); 11 for(int i = 1; i <= n; i++) 12 scanf("%lf", a+i); 13 sort(a+1, a+n+1); 14 for(int i = 1; i <= n; i++) { 15 sum1[i] = sum1[i-1]+a[i]; 16 sum2[i] = sum2[i-1]+a[i]*a[i]; 17 } 18 for(int i = 1; i <= n; i++) 19 for(int j = i+1; j <= n; j++){ 20 double mid = (sum1[j]-sum1[i-1])/(j-i+1); 21 c[i][j] = (sum2[j]-sum2[i-1])+(j-i+1)*mid*mid-2*mid*(sum1[j]-sum1[i-1]); 22 } 23 for(int i = 1; i <= m; i++) 24 for(int j = 1; j <= n; j++) 25 dp[i][j] = inf; 26 for(int i = 1; i <= n; i++) dp[1][i] = c[1][i]; 27 for(int i = 2; i <= m; i++){ 28 opt[i][n+1] = n; 29 for(int j = n; j >= 1; j--){ 30 for(int k = opt[i-1][j]; k <= opt[i][j+1]; k++) 31 if(dp[i][j] > dp[i-1][k]+c[k+1][j]){ 32 opt[i][j] = k; 33 dp[i][j] = dp[i-1][k]+c[k+1][j]; 34 } 35 } 36 } 37 printf("%.3lf ", dp[m][n]); 38 return 0; 39 }
决策单调且不要求在线的糙快猛优化方法(for内写成i <= dr && i < m会有bug,m = 1的时候,循环无法进入,导致f[1]值无穷大)
1 //个人感觉适用于状态数为二维的dp, 下一行(列)由前一行(列)推出, 且满足决策单调性 2 void solve(int l, int r, int dl, int dr) { 3 if(l > r) return ; 4 int m = (l+r) >> 1, dm; 5 for(int i = dl; i <= dr; i++) { 6 if(i更新f[m]更优) { 7 dm = i; 8 更新f[m]; 9 } 10 } 11 solve(l, m-1, dl, dm), solve(m+1, r, dm, dr); 12 }
基于决策单调的糙猛快分治
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 2010; 4 const double inf = 1e17; 5 double dp[N][N], c[N][N]; 6 double a[N], sum1[N], sum2[N], opt[N][N]; 7 void solve(double *f1, double *f2, int l, int r, int dl, int dr) { 8 if(l > r) return ; 9 int m = (l+r) >> 1, dm; 10 for(int i = dl; i <= dr; i++) { 11 if(f1[i]+c[i+1][m] < f2[m]) 12 f2[m] = f1[i]+c[i+1][m], dm = i; 13 } 14 solve(f1, f2, l, m-1, dl, dm); 15 solve(f1, f2, m+1, r, dm, dr); 16 } 17 int main() { 18 int n, m; 19 scanf("%d%d",&n,&m); 20 for(int i = 1; i <= n; i++) 21 scanf("%lf", a+i); 22 sort(a+1, a+n+1); 23 for(int i = 1; i <= n; i++) { 24 sum1[i] = sum1[i-1]+a[i]; 25 sum2[i] = sum2[i-1]+a[i]*a[i]; 26 } 27 for(int i = 1; i <= n; i++) 28 for(int j = i+1; j <= n; j++){ 29 double mid = (sum1[j]-sum1[i-1])/(j-i+1); 30 c[i][j] = (sum2[j]-sum2[i-1])+(j-i+1)*mid*mid-2*mid*(sum1[j]-sum1[i-1]); 31 } 32 33 for(int i = 0; i <= m; i++) 34 for(int j = 1; j <= n; j++) 35 dp[i][j] = inf; 36 for(int i = 1; i <= n; i++) dp[1][i] = c[1][i]; 37 for(int i = 2; i <= m; i++) 38 solve(dp[i-1], dp[i], 1, n, 1, n); 39 printf("%.3f ", dp[m][n]); 40 return 0; 41 }
eg. hiho1933
四边形不等式优化
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e4+5; 4 5 long long a[N], sum[N], dp[N][1005]; 6 int opt[N][1005]; 7 8 long long get_sum(int l, int r) { 9 int m = (l+r) >> 1; 10 long long ans1 = a[m]*(m-l)-(sum[m-1]-sum[l-1]); 11 long long ans2 = (sum[r]-sum[m])-a[m]*(r-m); 12 return ans1+ans2; 13 } 14 15 int main() { 16 int n, k; cin >> n >> k; 17 for(int i = 1, x, y; i <= n; i++) { 18 cin >> x >> y; 19 a[i] = y; 20 } 21 sort(a+1, a+n+1); 22 for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+a[i]; 23 24 memset(dp, 0x3f, sizeof(dp)); 25 for(int i = 0; i <= k; i++) dp[0][i] = 0; //这里i <= k写成了i <= n, 一直WA 26 for(int i = 1; i <= n; i++) { 27 opt[i][k+1] = n; 28 for(int j = k; j; j--) { 29 for(int kk = opt[i-1][j]; kk <= opt[i][j+1]; kk++) { 30 long long tmp = dp[kk][j-1]+get_sum(kk+1, i); 31 if(tmp < dp[i][j]) 32 dp[i][j] = tmp, opt[i][j] = kk; 33 } 34 } 35 } 36 cout << dp[n][k] << endl; 37 return 0; 38 }
基于决策单调的糙猛快分治
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e4+5; 4 5 long long a[N], sum[N], dp[2][N]; 6 7 long long get_sum(int l, int r) { 8 if(l >= r) return 0; 9 int m = (l+r) >> 1; 10 long long ans1 = a[m]*(m-l)-(sum[m-1]-sum[l-1]); 11 long long ans2 = (sum[r]-sum[m])-a[m]*(r-m); 12 return ans1+ans2; 13 } 14 15 void solve(long long *f1, long long *f2, int l, int r, int dl, int dr) { 16 if(l > r) return ; 17 int m = (l+r) >> 1, dm; 18 for(int i = dl; i <= dr; i++) { 19 long long tmp = f1[i]+get_sum(i+1, m); 20 if(tmp < f2[m]) { 21 f2[m] = tmp; 22 dm = i; 23 } 24 } 25 solve(f1, f2, l, m-1, dl, dm); 26 solve(f1, f2, m+1, r, dm, dr); 27 } 28 29 int main() { 30 int n, k; cin >> n >> k; 31 for(int i = 1, x, y; i <= n; i++) { 32 cin >> x >> y; 33 a[i] = y; 34 } 35 sort(a+1, a+n+1); 36 for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+a[i]; 37 38 for(int i = 0; i <= n; i++) 39 dp[1][i] = get_sum(1, i); 40 for(int i = 2; i <= k; i++) { 41 bool now = (i&1); 42 memset(dp[now], 0x3f, sizeof(dp[now])); 43 solve(dp[!now], dp[now], 1, n, 1, n); 44 } 45 cout << dp[k&1][n] << endl; 46 return 0; 47 }
二. 斜率优化
eg. HDU3507
f[i] = min(f[j]+(s[i]-s[j])²)+M
= min(f[j]+s[j]²-2s[i]s[j])+M+s[i]²
= min(kx+b)+M+s[i]², 其中k = -2s[j], b = f[j]+s[j]², x = s[i]
注:你也可看作k = 2s[i], x = s[j], y = f[j]+s[j]², f[i] = min(y-kx), k是定值,
则求k为定值的直线y = kx+b的最小截距。脑补画面,将斜率为k的直线移到最下端....
个人认为将整个min里的值当作y,当前已知值作为自变量比较好...
其它题目的变形:f[i]=max{x[j]*a[i]+y[j]*b[i]} = b[i]*max{a[i]/b[i]*x[j]+y[j]}
因为求min,所以维护倒U形(上凸)
因为k递减,x递增,所以用单调队列维护即可,队列内直线按k递减排序,维护倒U形。
代码区===待填===
ps.
如果x无序,则每次二分查找;
如果k无序,可以用set维护,插入的时候erase掉两侧不符合的直线;
如果x, k都无序,可以用set按k值排序维护直线,查找的时候再二分k,lowerbound计算在哪条斜率的直线上(判断x值是否在该直线的两端点内,不在则继续二分)
三、WQS二分
作用就是题目给了一个选物品的限制条件,要求刚好选m个,让你最大化(最小化)权值,然后其特点就是当选的物品越多的时候权值越大(越小)。
http://codeforces.com/blog/entry/49691
https://www.cnblogs.com/HocRiser/p/9834069.html
https://www.cnblogs.com/flashhu/p/9480669.html
https://www.cnblogs.com/CreeperLKF/p/9045491.html
=================================================================================================
区间dp
括号dp:
hihocoder1891:有多少种填充*的方案使得括号匹配?
1 #include <bits/stdc++.h> 2 3 #define ll long long 4 #define ull unsigned long long 5 #define st first 6 #define nd second 7 #define pii pair<int, int> 8 #define pil pair<int, ll> 9 #define pli pair<ll, int> 10 #define pll pair<ll, ll> 11 #define tiii tuple<int, int, int> 12 #define pw(x) ((1LL)<<(x)) 13 #define lson l, m, rt<<1 14 #define rson m+1, r, rt<<1|1 15 #define sqr(x) ((x)*(x)) 16 #define SIZE(A) ((int)(A.size())) 17 #define LENGTH(A) ((int)(A.length())) 18 #define FIN freopen("A.in","r",stdin); 19 #define FOUT freopen("A.out","w",stdout); 20 using namespace std; 21 /***********/ 22 23 template<typename T> 24 bool scan (T &ret) { 25 char c; 26 int sgn; 27 if (c = getchar(), c == EOF) return 0; //EOF 28 while (c != '-' && (c < '0' || c > '9') ) c = getchar(); 29 sgn = (c == '-') ? -1 : 1; 30 ret = (c == '-') ? 0 : (c - '0'); 31 while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0'); 32 ret *= sgn; 33 return 1; 34 } 35 template<typename N,typename PN>inline N flo(N a,PN b){return a>=0?a/b:-((-a-1)/b)-1;} 36 template<typename N,typename PN>inline N cei(N a,PN b){return a>0?(a-1)/b+1:-(-a/b);} 37 template<typename T>inline int sgn(T a) {return a>0?1:(a<0?-1:0);} 38 template<class T> int countbit(const T &n) { return (n==0)?0:(1+countbit(n&(n-1))); } 39 template <class T1, class T2> 40 bool gmax(T1 &a, const T2 &b) { return a < b? a = b, 1:0;} 41 template <class T1, class T2> 42 bool gmin(T1 &a, const T2 &b) { return a > b? a = b, 1:0;} 43 template <class T> inline T lowbit(T x) {return x&(-x);} 44 45 template<class T1, class T2> 46 ostream& operator <<(ostream &out, pair<T1, T2> p) { 47 return out << "(" << p.st << ", " << p.nd << ")"; 48 } 49 template<class A, class B, class C> 50 ostream& operator <<(ostream &out, tuple<A, B, C> t) { 51 return out << "(" << get<0>(t) << ", " << get<1>(t) << ", " << get<2>(t) << ")"; 52 } 53 template<class T> 54 ostream& operator <<(ostream &out, vector<T> vec) { 55 out << "("; for(auto &x: vec) out << x << ", "; return out << ")"; 56 } 57 void testTle(int &a){ 58 while(1) a = a*(ll)a%1000000007; 59 } 60 const ll inf = 0x3f3f3f3f; 61 const ll INF = 1e17; 62 const int mod = 1000000007;; 63 const double eps = 1e-5; 64 const int N = 100000+10; 65 const double pi = acos(-1.0); 66 67 /***********/ 68 69 char s[N]; 70 long long dp[2][N]; 71 void add(long long &x, long long y) { 72 x += y; 73 if(x >= mod) x %= mod; 74 } 75 int main() { 76 scanf("%s", s); 77 int tag = 0, modify = 0, base = 0, l = 1; 78 dp[0][0] = 1; 79 for(int i = 0; s[i]; i++) { 80 if(s[i] == '(') 81 base++; 82 else if(s[i] == ')') { 83 if(base) base--; 84 else { 85 l--; 86 if(l == 0) { puts("0"); return 0; } 87 modify++; 88 } 89 } 90 else { 91 if(modify) { 92 tag = !tag; 93 for(int j = 0; j < l; j++) 94 dp[tag][j] = dp[!tag][j+modify]; 95 modify = 0; 96 } 97 tag = !tag; 98 if(base) { 99 memset(dp[tag], 0, sizeof(long long)*(l+2)); 100 for(int j = 0; j < l; j++) { 101 add(dp[tag][j], dp[!tag][j]); 102 add(dp[tag][j+2], dp[!tag][j]); 103 } 104 base--, l += 2; 105 } 106 else { 107 memset(dp[tag], 0, sizeof(long long)*(l+1)); 108 for(int j = 0; j < l; j++) { 109 if(j) add(dp[tag][j-1], dp[!tag][j]); 110 add(dp[tag][j+1], dp[!tag][j]); 111 } 112 l++; 113 } 114 } 115 } 116 if(modify) { 117 tag = !tag; 118 for(int j = 0; j < l; j++) 119 dp[tag][j] = dp[!tag][j+modify]; 120 modify = 0; 121 } 122 printf("%lld ", base? 0: dp[tag][0]); 123 return 0; 124 }