\(eg:\) \(f[i] = min(f[j] + val(i,j)) (Li \leq j \leq Ri)\)
参考例题P1886 滑动窗口 /【模板】单调队列
#include <iostream> #include <cstdio> #define Re register int using namespace std; inline int read(){ int x = 0, f = 1; char c; while(!isdigit(c = getchar())) if(c == '-') f = -1; do x = (x << 1) + (x << 3) + (c ^ 48); while(isdigit(c = getchar())); return x * f; } const int N = 1e6+6; int n,k,a[N],q[N],l,r; int main(){ // freopen("T.in","r",stdin); // freopen("T.out","w",stdout); n = read(); k = read(); for(Re i = 1; i <= n; ++i) a[i] = read(); l = 1; r = 0; for(Re i = 1; i < k; ++i){ while(l <= r && a[q[r]] > a[i]) r--; q[++r] = i; } for(Re i = k; i <= n; ++i){ while(l <= r && i - q[l] + 1 > k) ++l; while(l <= r && a[q[r]] > a[i]) --r; q[++r] = i; printf("%d ",a[q[l]]); } putchar('\n'); l = 1;r = 0; for(Re i = 1; i < k; ++i){ while(l <= r && a[q[r]] < a[i]) r--; q[++r] = i; } for(Re i = k; i <= n; ++i){ while(l <= r && i - q[l] + 1 > k) ++l; while(l <= r && a[q[r]] < a[i]) --r; q[++r] = i; printf("%d ",a[q[l]]); } putchar('\n'); return 0; }
列dp方程:f[i][j]表示前i天手里有j股的时候所能赚的最多的钱
考虑转移:
考虑优化:
对于买入的转移,发现j和\(j-1\)是有重合部分的,类似于滑动窗口的变化趋势
且\(dp\)式子有只与决策点\(k\)有关的式子,所以可以单调队列进行优化
卖出同理
#include <iostream> #include <cstdio> #include <cstring> #define Re register int using namespace std; inline int read(){ int x = 0, f = 1; char c; while(!isdigit(c = getchar())) if(c == '-') f = -1; do x = (x << 1) + (x << 3) + (c ^ 48); while(isdigit(c = getchar())); return x * f; } const int N = 2004; int T,MaxP,W,Ap[N],Bp[N],As[N],Bs[N],f[N][N]; int l,r,q[N]; int main(){ // freopen("T.in","r",stdin); // freopen("T.out","w",stdout); T = read(); MaxP = read(); W = read(); for(Re i = 1; i <= T; ++i){ Ap[i] = read(); Bp[i] = read(); As[i] = read(); Bs[i] = read(); } memset(f,-0x3f,sizeof(f)); for(Re i = 1; i <= T; ++i){ for(Re j = 0; j <= As[i]; ++j) f[i][j] = max(f[i][j],-Ap[i] * j); for(Re j = 0; j <= MaxP; ++j) f[i][j] = max(f[i][j],f[i - 1][j]); if(i <= W) continue; l = 1; r = 0; for(Re j = 0; j <= MaxP; ++j){ while(l <= r && q[l] < j - As[i]) ++l; if(l <= r) f[i][j] = max(f[i][j],f[i - W - 1][q[l]] - j * Ap[i] + q[l] * Ap[i]); while(l <= r && f[i - W - 1][q[r]] + q[r] * Ap[i] <= f[i - W - 1][j] + j * Ap[i]) --r; q[++r] = j; } l = 1; r = 0; for(Re j = MaxP; j >= 0; --j){ while(l <= r && q[l] > j + Bs[i]) ++l; if(l <= r) f[i][j] = max(f[i][j],f[i - W - 1][q[l]] + q[l] * Bp[i] - j * Bp[i]); while(l <= r && f[i - W - 1][q[r]] + q[r] * Bp[i] <= f[i - W - 1][j] + j * Bp[i]) --r; q[++r] = j; } } int ans = -0x3f3f3f3f; for(Re i = 0; i <= MaxP; ++i) ans = max(ans,f[T][i]); printf("%d\n",ans); return 0; }
设\(f[i][j][t]\)为第\(t\)个时间在\((i,j)\)的最大滑动距离
转移\(f[i][j][t] = max(f[i'][j'][t - 1] + dis,f[i][j][t-1])\)
\(Then\) \(you\) \(will\) \(get\) \(MLE.\)
一段时间段内的滑动方向一致
设\(f[i][j][k]\)为\(k\)个时间段后在\((i,j)\)时的最大滑动距离
转移\(f[i][j][k] = max(f[i'][j'][k-1] + dis,f[i][j][k])\)
\(O(kn^3)\)
\(Then\) \(you\) \(will\) \(get\) \(TLE.\)
考虑优化:
每个时间段内都是在同一列或者同一行滑动,联想到滑动窗口,可以进行单调队列优化
#include <iostream> #include <cstring> #include <cstdio> #define Re register int using namespace std; inline int read(){ int x = 0, f = 1; char c; while(!isdigit(c = getchar())) if(c == '-') f = -1; do x = (x << 1) + (x << 3) + (c ^ 48); while(isdigit(c = getchar())); return x * f; } const int N = 203; int dx[5] = {0,-1,1,0,0}, dy[5] = {0,0,0,-1,1}; int n,m,x,y,K,f[N][N],l,r,ans; char Map[N][N]; struct Node{int dp,pos;}q[N]; void solve(int x,int y,int len,int d){ l = 1; r = 0; for(Re i = 1; ; ++i, x += dx[d], y += dy[d]){ if(x <= 0 || x > n || y <= 0 || y > m) break; if(Map[x][y] == 'x') l = 1, r = 0; else{ while(l <= r && q[r].dp + i - q[r].pos <= f[x][y]) --r; q[++r] = (Node){f[x][y],i};//这里需要先加入队尾,这样保证队列里的值都是上一个时间段的 while(l <= r && i - q[l].pos > len) ++l; f[x][y] = max(f[x][y],q[l].dp + i - q[l].pos); ans = max(f[x][y],ans); } } } int main(){ // freopen("T.in","r",stdin); // freopen("T.out","w",stdout); n = read(); m = read(); x = read(); y = read(); K = read(); for(Re i = 1; i <= n; ++i) scanf(" %s ",Map[i] + 1); memset(f,-0x3f,sizeof(f)); f[x][y] = 0; for(Re i = 1; i <= K; ++i){ int s = read(), t = read(), d = read(); int len = t - s + 1; if(d == 1) for(Re j = 1; j <= m; ++j) solve(n,j,len,d); if(d == 2) for(Re j = 1; j <= m; ++j) solve(1,j,len,d); if(d == 3) for(Re j = 1; j <= n; ++j) solve(j,m,len,d); if(d == 4) for(Re j = 1; j <= n; ++j) solve(j,1,len,d); } printf("%d\n",ans); return 0; }
\(eg: f[i] = min(f[j] + val(i,j))\)
其中\(val(i,j)\)既与\(i\)相关又与\(j\)相关,比如\(2 * x[i] * x[j]\)
此时需要用到斜率优化
两点间连线斜率:\(\frac{y1 - y2}{x1 - x2}\)
(From here)
【APIO2010】特别行动队
不难得出
\(dp[i] = max(dp[j] + a \times (sumi - sumj) ^ 2 + b \times (sumi- sumj) + c)\)
其中\(j\)作为决策点,\(i\)的值固定,所以把只与\(i\)相关的量视为常量
剩下的式子为 \(dp[j] - 2 \times a \times sumi \times sumj + a \times sumj^2 - b \times sumj\)
对于式子中只与\(j\)相关的项,无论\(i\)如何变这个值都是不变的。所以可以将这些项的和压缩为一个函数\(y\)
令\(y(j) = dp[j] + a \times sumj^2 - b \times sumj\)
式子变为\(y(j) + (-2 \times a \times sumi) \times sumj\)
令\(k(i) = -2 \times a \times sumi\),式子变为\(y(j) + k(i) \times sumj\)
从上述可得,如果两个相连的直线的斜率不大于\(k = 2 \times a \times sumi\),那么前面那个店对应的j就一定不是最优决策点;反之,后面那个点就一定不是最优决策点。
由此推广到更多的点:如果前两个点的斜率大于后两个点的斜率,那么把中间的点删去
因为此题中a 是负的\(,k\)一定是小于\(0\)的,所以维护上凸包
#include <iostream> #include <cstdio> #define int long long #define Re register int #define K(i) (2 * a * sum[i]) #define X(i) (sum[i]) #define Y(i) (f[i] + a * sum[i] * sum[i] - b * sum[i]) using namespace std; inline int read(){ int x = 0, f = 1; char c; while(!isdigit(c = getchar())) if(c == '-') f = -1; do x = (x << 1) + (x << 3) + (c ^ 48); while(isdigit(c = getchar())); return x * f; } const int N = 1e6+6; int n,sum[N],a,b,c,l,r,q[N],f[N]; double slope(int i,int j){ return 1.0 * (Y(i) - Y(j)) / (X(i) - X(j)); } signed main(){ // freopen("T.in","r",stdin); // freopen("T.out","w",stdout); n = read(); a = read(); b = read(); c = read(); for(Re i = 1; i <= n; ++i) sum[i] = read(), sum[i] += sum[i - 1]; // for(Re i = 1; i <= n; ++i) cout << sum[i] << " "; cout << endl; for(Re i = 1; i <= n; ++i){ while(l < r && slope(q[l],q[l + 1]) > K(i)) ++l; f[i] = -K(i) * X(q[l]) + Y(q[l]) + a * sum[i] * sum[i] + b * sum[i] + c; while(l < r && slope(q[r - 1],q[r]) <= slope(q[r],i)) --r; //因为k<0所以维护的是上凸壳 q[++r] = i; } printf("%lld\n",f[n]); return 0; }