DP优化 单调队列优化 前缀和优化 斜率优化 二分栈
前置技能:单调队列(经典的问题模型:洛谷P1886 滑动窗口)
对于序列中的每个点,从其左侧一段决策区间内的最值进行转移,且决策区间随着序列下标的增大也不断右移(就像窗口向右滑动)。
设永远不会成为最优决策点,再也不会被转移了。
于是,我们只要维护一个队列,满足下标递增,决策性递减。我们需要当前的队首成为最优决策点,那么当队首第一次超出了区间范围(以后也就永远超出了)就把它出队。为了保证单调性,队尾新加入点之前,要先把队列中比它劣的点依次从队尾出队。
题解:
两个单调队列一个最大一个最小
然后?
就可以了啊
1、维护队首(就是如果你已经是当前的m个之前那你就可以被删了,head++)
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态)
#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cmath> using namespace std; int n,m; int q1[1000001],q2[1000001]; int a[1000001]; int min_deque() { int h=1,t=0; for(int i=1;i<=n;i++) { while(h<=t&&q1[h]+m<=i) h++; while(h<=t&&a[i]<a[q1[t]]) t--; q1[++t]=i; if(i>=m) printf("%d ",a[q1[h]]); } cout<<endl; } int max_deque() { int h=1,t=0; for(int i=1;i<=n;i++) { while(h<=t&&q2[h]+m<=i) h++; while(h<=t&&a[i]>a[q2[t]]) t--; q2[++t]=i; if(i>=m) printf("%d ",a[q2[h]]); } } int main() { cin>>n>>m; for(int i=1;i<=n;i++) scanf("%d",&a[i]); min_deque(); max_deque(); return 0; }
前缀和优化
当DP过程中需要反复从一个求和式转移的话,可以先把它预处理一下。运算一般都要满足可减性。
比较简单就不展开了
题解:
考虑i的放置,i为最大值,所以放在i-1个位置都可以计算出对答案的贡献
dp[i][j]=Σdp[i-1][k] (j-i+1 <=k<= j)
特别的到i时最多可以贡献i-1对逆序对,所以从dp[0]~dp[j-i+1]这一段不能加
n^3超时,可用前缀和优化
#include<iostream> #include<cstdio> #include<cstring> #define N 1001 #define mod 10000 using namespace std; int dp[N][N]; int n,k,ans,sum; int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) dp[i][0]=1; for(int i=2;i<=n;i++) { sum=0; for(int j=0;j<=k;j++) { (sum+=dp[i-1][j])%mod; dp[i][j]=sum%mod; if(j-i+1>=0)((sum-=dp[i-1][j-i+1])+=mod)%mod; } } printf("%d ",dp[n][k]); return 0; }
斜率优化
与决策单调性有着说不清的联系。
考虑两个决策
这时候根据题目特点把xj,yj)分布在坐标系上,而真正有用的决策点实际上形成了一个凸壳。(由类似线性规划的寻找最优解的过程可以发现)
下面第一题对两种理解斜率优化的方法做了更详细的分析(最好看第二种,因为第一种有针对性,不通用)
于是我们用数据结构维护凸壳上的所有点,具体实现依题而定。
不管是什么题,加入决策点的时候要保证斜率递增/递减。
如果x单调,可以用单调栈维护凸壳,转移时使用当前直线的斜率(线性规划),在栈内二分最优解。
如果斜率k单调,那么用单调队列维护凸壳,队首为当前最优决策。转移之前如果队首不优就出队。
洛谷P2900 [USACO08MAR]土地征用Land Acquisition
题解:
斜率优化DP入门题。
观察题目发现如果有两块土地 i, ji,j 满足 xi ≤ xj , yi ≤ yj i 的影响就可以不用考虑。
因此把土地按长度从小到大排序,则它们的宽度递减。可以发现每次购买的土地一 定是连续一段。
我们设 f(i)表示买前 i块需要的最小代价
斜率优化,维护一个下凸包即可。
#include <bits/stdc++.h> using namespace std; inline int read() { int x=0; int f=1; char ch=getchar(); while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();} while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} return x*f; } const int MAXN = 100005; struct data {long long x,y;} a[MAXN]; long long x[MAXN],y[MAXN],f[MAXN],q[MAXN]; int n,tot; inline bool cmp(data a,data b) {return a.x==b.x?a.y<b.y:a.x<b.x;} inline double slop(int a,int b) {return (double)(f[b]-f[a])/(y[a+1]-y[b+1]);} int main(int argc, char const *argv[]) { n=read(); for(int i=1; i<=n; i++) a[i].x=read(),a[i].y=read(); sort(a+1,a+n+1,cmp); for(int i=1; i<=n; i++) { while(tot && a[i].y>=y[tot]) tot--; x[++tot]=a[i].x; y[tot]=a[i].y; } int l=0,r=0; for(int i=1; i<=tot; i++) { while(l<r && slop(q[l],q[l+1])<x[i]) l++; int t=q[l]; f[i]=f[t]+y[t+1]*x[i]; while(l<r && slop(q[r],i)<slop(q[r-1],q[r])) r--; q[++r]=i; } printf("%lld",f[tot]); return 0; }
二分栈
我们使用决策二分栈(一种单调栈)来维护所有有用的决策,其中栈顶是当前最优决策。
为什么叫二分栈呢?
我们可以把k以后另一个更优。可以借助函数图像来理解这个过程。
我们需要栈顶为当前的最优解。而如果栈中有不止一个元素,则可能存在一个i之后栈里面的决策比栈顶优了。这个时候,如何快速判断并弹掉栈顶呢?
上面提到的这个可二分的性质就派上用场了。对于当前的之前就比栈顶更优了,就要把栈顶弹掉。