DP优化总结
来源:互联网 发布:java poi 自适应宽度 编辑:程序博客网 时间:2024/04/27 20:33
- 矩阵优化DP
- 例子
- fib数列
- fib数列拓展
- kmp转移
- 小型图的转移
- 例子
- 决策单调栈优化
- 例子
- 玩具装箱Toy
- 土地购买
- 例子
- 单调队列优化DP
- 例子
- 单调队列维护决策
- 单调队列维护可选决策
- 基环外向树的直径
- 多重背包的OnmOnm优化
- 例子
- 斜率优化
- 决策直线的斜率与二元组的横坐标同时满足单调性
- 例题
- 土地购买
- 玩具装箱Toy
- 仓库建设
- 特别行动队
- 不满足斜率单调性
- 货币兑换Cash
- 矩阵优化DP
矩阵优化DP
满足三个特点:
1.转移要选取的决策较少。(一般在常数级别)
2.转移的步骤很多。(一般是1e10以上的级别)
3.每一步的转移方程一样。(和递推类似)
*一般满足转移方程:
例子:
fib数列
一般转移方程为
不放设矩阵
每次要得到另一个矩阵
转移矩阵已经很明显:
显然,
fib数列拓展:
1.
转移
转移矩阵
时间复杂度
2.
转移
转移矩阵
时间复杂度
kmp转移
“GT考试”题解
小型图的转移
“迷路”题解
决策单调栈优化
如果转移满足两个特点:
1.转移方程:
2.
那么称这个转移满足决策单调性:
考虑优化:
1.
有时候可以达到
2.
严格
例子
玩具装箱Toy
题意:
有
题解:
打表观察到决策单调性后直接二分解决。这里贴一份代码:
#include<bits/stdc++.h>using namespace std;typedef pair<int,int> pii;typedef long long ll;streambuf *ib,*ob;inline int read(){ char ch=ib->sbumpc();int i=0,f=1; while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();} while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();} return i*f;}int buf[80];inline void W(ll x){ if(!x){ob->sputc('-');return;} if(x<0){ob->sputc('-');x=-x;} while(x){buf[++buf[0]]=x%10,x/=10;} while(buf[0])ob->sputc(buf[buf[0]--]+'0');}const int Maxn=5e4+50;int n,head,tail;ll len[Maxn],L,f[Maxn];pii q[Maxn];inline ll calc(int i,int j){return (j-i+len[j]-len[i-1]-L)*(j-i+len[j]-len[i-1]-L);}inline int solve(int o,int now,int l,int r){ int ans=0; while(l<=r) { int mid=(l+r)>>1; if(f[o]+calc(o+1,mid)>=f[now]+calc(now+1,mid))ans=mid,r=mid-1; else l=mid+1; } return ans;}int main(){ ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);ib=cin.rdbuf();ob=cout.rdbuf(); n=read(),L=read(); for(int i=1;i<=n;i++)len[i]=1ll*read()+len[i-1]; q[head=tail=1]=make_pair(1,0); for(int i=1;i<=n;i++) { f[i]=f[q[head].second]+calc(q[head].second+1,i); ((++q[head].first)>=q[head+1].first&&tail>head)?(head++):0; int pos=n; while(tail>=head&&(f[i]+calc(i+1,q[tail].first)<=f[q[tail].second]+calc(q[tail].second+1,q[tail].first)))pos=q[tail].first,tail--; if(tail<head)q[++tail]=make_pair(i+1,i); else { pos=solve(q[tail].second,i,q[tail].first,pos); if(pos)q[++tail]=make_pair(pos,i); } } W(f[n]);ob->sputc('\n');}
土地购买
题意:
有
题解:
显然对于长宽都含于另一个长方体的长方体可以忽略。
那么剩下的长方体排布形式一定为:
即
又有转移方程:
因为:
满足决策单调性。
单调队列优化DP
如果转移满足以下模型:
那么可以用单调队列维护决策表达到
怎么维护?
发现对于一个决策
例子
单调队列维护决策
“生产商品”题解
单调队列维护可选决策
*这类dp满足可选决策时单调的,但最优决策要在可选决策中选取最优值,一般用set或平衡树(Splay\Treap)维护.
如:
poj3017:Cut the Sequence
•问题描述
给定一个有n个非负整数的数列a,要求将其划分为若干个部分,使得每部分的和不超过给定的常数m,并且所有部分的最大值的和最小。其中n<=105。
例:n=8, m=17,8个数分别为2 2 2 | 8 1 8 |1 2,答案为12,分割方案如图所示。
•解法分析
刚开始拿到这道题目,首先要读好题:最大值的和最小。
首先设计出一个动态规划的方法
,
其中
直接求解复杂度最坏情况下(
几个性质
通过仔细观察,可以发现以下几点性质:
① 在计算状态
②
③ 来看一个最重要的性质:如果一个决策
单调队列优化可选决策
到此为止,我们可以这样做:由于性质三,每计算一个状态
这时候问题来了,队首元素一定是最佳决策点吗?我们只保证了他的元素值最大……如果扫一遍队列,只是常数上的优化,一个递减序足以将它否决。
我们观察整个操作,将队列不断插入、不断删除。对于除了队尾的元素之外,每个队列中的元素供当前要计算的状态的“值”是
我们发现,完成上述一系列工作的最佳选择就是平衡树,这样每个元素都插入、删除、查找各一遍,复杂度为
基环外向树的直径
例如:bzoj1791:Island
(这道题是真的难写。。)
首先,每一个基环外向树相当于一个环上有一些点挂了一颗子树,先把这个点的深度算出来,再考虑环上做DP的方法:
断开任意一条环边,再把这个环复制一遍,做
给一份代码,可以参考参考
#include<bits/stdc++.h>using namespace std;namespace IO{ streambuf *ib,*ob; int buf[50]; inline void init() { ios::sync_with_stdio(false); cin.tie(NULL);cout.tie(NULL); ib=cin.rdbuf();ob=cout.rdbuf(); } inline int read() { char ch=ib->sbumpc();int i=0,f=1; while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();} while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();} return i*f; } inline void W(long long x) { if(!x){ob->sputc('0');return;} if(x<0){ob->sputc('-');x=-x;} while(x){buf[++buf[0]]=x%10;x/=10;} while(buf[0])ob->sputc(buf[buf[0]--]+'0'); }}typedef long long ll;typedef pair<int,ll> pil;typedef pair<int,int> pii;const int Maxn=1e6+50;int n,ecnt=1,tail,tailtmp,bz,bg,last[Maxn],from[Maxn],vis[Maxn],ins[Maxn],id[Maxn];ll dp[Maxn][2],ans,res;pii statmp[Maxn];pil sta[Maxn];struct E{int to,val,nxt;}edge[Maxn*2];inline void add(int x,int y,int z){ edge[++ecnt].to=y;edge[ecnt].nxt=last[x];last[x]=ecnt;edge[ecnt].val=z; edge[++ecnt].to=x;edge[ecnt].nxt=last[y];last[y]=ecnt;edge[ecnt].val=z;}inline void dfs(int now,int val){ vis[now]=1;statmp[++tailtmp]=make_pair(now,val);id[now]=tailtmp; for(int e=last[now];e;e=edge[e].nxt) { int v=edge[e].to; if((e^1)==from[now])continue; if(!vis[v])from[v]=e,dfs(v,edge[e].val); else {statmp[id[v]].second=edge[e].val;bz=1;bg=id[v];return;} if(bz)return; } tailtmp--;}inline void dfs2(int now,int f=0){ vis[now]=1; for(int e=last[now];e;e=edge[e].nxt) { int v=edge[e].to; if(ins[v]||v==f)continue; dfs2(v,now); ll t=dp[v][0]+edge[e].val; if(t>=dp[now][0])dp[now][1]=dp[now][0],dp[now][0]=t; else if(t>dp[now][1])dp[now][1]=t; ans=max(ans,dp[now][1]+dp[now][0]); }}pil q[Maxn*2];int qhead,qtail;inline ll calc(int i){ for(int i=bg;i<=tailtmp;i++)sta[++tail]=statmp[i],ins[sta[tail].first]=1; for(int i=1;i<=tail;i++)dfs2(sta[i].first); memcpy(sta+tail+1,sta+1,sizeof(pil)*tail);int len=tail*2; for(int i=1;i<=len;i++)sta[i].second+=sta[i-1].second; ans=max(ans,dp[sta[1].first][0]);q[qhead=qtail=1]=make_pair(1,dp[sta[1].first][0]-sta[1].second); for(int i=2;i<=len;i++) { int lim=i-tail+1; while(q[qhead].first<lim) qhead++; ll t=dp[sta[i].first][0]+q[qhead].second+sta[i].second; ans=max(ans,t); t=dp[sta[i].first][0]-sta[i].second; while(qhead<=qtail&&q[qtail].second<=t)qtail--; q[++qtail]=make_pair(i,t); } return ans;}int main(){ IO::init();n=IO::read(); for(int i=1;i<=n;i++) { int y=IO::read(),z=IO::read(); add(i,y,z); } for(int i=1;i<=n;i++) { if(!vis[i]) { tailtmp=bg=bz=tail=ans=0; dfs(i,0); res+=calc(i); } } IO::W(res);IO::ob->sputc('\n');}
多重背包的(Onm) 优化
首先,多重背包的转移方程是:
如果决策连续就是裸的单调队列。现在考虑不连续:
首先可以发现一个
for(int i=1;i<=n;i++) { for(int j=0;j<c[i];j++) { q[head=tail=1]=make_pair(f[j],0); for(int k=j+c[i];k<=m;k+=c[i]) { int a=k/c[i],t=f[k]-a*w[i]; while(head<=tail&&q[tail].first<=t)tail--; q[++tail]=make_pair(t,a); while(head<=tail&&q[head].second+s[i]<a)head++; f[k]=max(f[k],q[head].first+a*w[i]); } } }
斜率优化
模型:
这个模型写的比较抽象, 其实它的涵盖范围是很广的。 首先, a[i], b[i]不一定要是常量, 只要他们与决策无关, 都可以接受; 另外, g(j)和 h(j)不管是常量还是变量都没有关系, 只要他们是一个由i决定的二元组就可以了。
为了方便描述, 把这个模型做如下转化:
可以发现,状态现在就是许多条直线,而我们的任务是选出一条直线,使它经过前面出现的某一点,且的纵截距最小。 可以想象有一组斜率相同的直线自负无穷向上平移, 所碰到的第一个数据点就是最优决策。
这个时候, 有一个重要的性质, 那就是: 所有最优决策点都在平面点集的凸包上。
基于这个事实, 我们可以开发出很多令人满意的算法。
这时, 根据直线斜率与数据点分布的特征, 可以划分为两种情况:
1.决策直线的斜率与二元组的横坐标同时满足单调性。
这样的模型是比较好处理的, 因为这个时候由于斜率变化是单调的, 所以决策点必然在凸壳上单调移动。我们只需要维护一个单调队列和一个决策指针,每次决策时作这样几件事:
1.决策指针( 也就是队首) 后移, 直至最佳决策点。
2.进行决策。
3.将进行决策之后的新状态的二元组加入队尾, 同时作
算法的时间复杂度为
例题
土地购买
再来观察这个转移方程:
转化后:
很明显,斜率
#include<iostream>#include<cmath>#include<algorithm>#include<cstdio>using namespace std;const int Maxn=5e4+50;streambuf *ib,*ob;typedef long long ll;inline void init(){ ios::sync_with_stdio(false); cin.tie(NULL);cout.tie(NULL); ib=cin.rdbuf();ob=cout.rdbuf();}inline int read(){ char ch=ib->sbumpc();int i=0,f=1; while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();} while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();} return i*f;}int buf[50];inline void W(ll x){ if(!x){ob->sputc('0');return;} if(x<0){ob->sputc('-');x=-x;} while(x)buf[++buf[0]]=x%10,x/=10; while(buf[0])ob->sputc(buf[buf[0]--]+'0');}struct point{ ll x,y; point(ll x=0,ll y=0):x(x),y(y){} friend inline point operator -(const point &a,const point &b) { return point(a.x-b.x,a.y-b.y); } friend inline ll dot(const point &a,const point &b) { return a.x*b.y-a.y*b.x; }}p[Maxn],q[Maxn];int n,tot,head,tail;ll f[Maxn];inline bool comp(const point &a,const point &b){ return a.y>b.y||(a.y==b.y&&a.x>b.x);}inline ll calc(int pos,int x){ return q[pos].y+p[x].x*q[pos].x;}int main(){ init(); n=read(); for(int i=1;i<=n;i++) { int x=read(),y=read(); p[i]=point(x,y); } sort(p+1,p+n+1,comp); int mx=p[tot=1].x; for(int i=2;i<=n;i++) { if(p[i].x<=mx)continue; p[++tot]=p[i];mx=p[i].x; } q[head=tail=1]=point(p[1].y,0); for(int i=1;i<=tot;i++) { while(head<tail&&calc(head,i)>=calc(head+1,i))head++; f[i]=calc(head,i); while(tail>=2&&dot(point(p[i+1].y,f[i])-q[tail-1],q[tail]-q[tail-1])<=0)tail--; q[++tail]=point(p[i+1].y,f[i]); if(head>tail)head=tail; } W(f[tot]);ob->sputc('\n');}
玩具装箱Toy
转移方程:
设
仓库建设
设所有前面的仓库转移到该仓库花费费用为
设
又有:
特别行动队
同样写出来可以斜率优化。
(a只有小于0维护上凸包,若a大于0则维护下凸包)
不满足斜率单调性
这个没什么好说,用平衡树维护,做到
货币兑换Cash
转移方程
不满足单调,一般两种解法:
1.CDQ分治,保证处理完左边之后维护左半凸包并处理右边:http://paste.ubuntu.com/25699099/
2.set(平衡树)维护凸包,当然这种操作没有CDQ简便,最好写CDQ.:http://paste.ubuntu.com/25699108/
- DP斜率优化总结
- 四边形优化dp总结
- 5.20-DP优化总结
- DP优化总结
- 关于矩阵优化的DP总结
- 逊哥dp专题 总结(普通dp,斜率优化dp,数位dp)
- dp部分总结(单调队列,四边形优化,斜率优化,树形dp)
- 12.9 省选训练总结3(2) DP的优化
- dp总结
- DP总结
- dp总结
- DP总结
- DP总结
- dp总结
- dp总结
- DP总结
- dp总结
- dp总结
- werkzeug实现简单Python web框架(2):添加jinjia2模板支持
- 15个高级的PostgreSQL命令的例子
- android Gide加载webp图片方法
- mock.js的真实数据模拟
- spring+SpringMVC+mybati整合JBPM
- DP优化总结
- dcloud (h5) 页面内容布局(需要计算手机屏幕)
- Subsequence
- java反射之Method类中invoke()方法的使用
- maven中groupid和artifactid意思
- python 爬虫系列01 认识 uillib
- SAMBA配置
- HBase 常用命令
- mysql的4种复制操作详解(上篇)