浅谈DP优化

其实这个写起来是自己总结用的,当然如果能够帮助到你,那也是非常棒的一件事情
大部分的《算法竞赛进阶指南》上面都有,但这是我自己做的一些整理和总结。

DP其实在现在已经算是考烂掉的题目了,类型很多,题目很杂,但思想大概就是从已有结果当中选出当前阶段的结果了,本身是对于暴力枚举的最优性优化,利用记忆化减少了前置阶段的枚举。

DP优化其实是在枚举前置状态上的一个更优解的选择从而减少枚举的转移对象。复杂度大概都是从 O ( n 2 ) O(n^2) O(n2)优化到 O ( n ) − O ( n l o g n ) O(n)-O(nlogn) O(n)O(nlogn)不等。

主要利用到的工具是双端队列(单调队列),利用单调性维护所选择的决策转移方案。STL库当中也有,但这个其实手动维护一下就好了(STL真的很慢)。


数据结构优化

这一个优化主要是面对决策选择集合只会增大,不会减少的决策集合当中进行选择,就可以利用二叉堆,线段树,树状数组等等较高级的数据结构进行最优决策的优化。

【例题1】Cleaning Shifts(POJ2376

Farmer John is assigning some of his N (1 <= N <= 25,000) cows to do some cleaning chores around the barn. He always wants to have one cow working on cleaning things up and has divided the day into T shifts (1 <= T <= 1,000,000), the first being shift 1 and the last being shift T.

Each cow is only available at some interval of times during the day for work on cleaning. Any cow that is selected for cleaning duty will work for the entirety of her interval.

Your job is to help Farmer John assign some cows to shifts so that (i) every shift has at least one cow assigned to it, and (ii) as few cows as possible are involved in cleaning. If it is not possible to assign a cow to each shift, print -1.


这个方程其实比较好写 f [ b i ] = m i n { f [ x ] } + 1 ( a i − 1 ≤ x &lt; b i ) f[b_i]=min\left\{ f[x] \right\}+1 (a_i-1\leq x &lt;b_i) f[bi]=min{ f[x]}+1(ai1x<bi)

但是显然这个的在枚举先前决策的时候数据太大,接近于 O ( n 2 ) O(n^2) O(n2),所以需要利用数据结构维护转移方程当中的单调性。利用堆写一个struct,找出在条件范围内的最优解进行转移,从 O ( n 2 ) O(n^2) O(n2)优化到了 O ( n log ⁡ n ) O(n\log n) O(nlogn),就是有一个不小的突破。

#include <bits/stdc++.h> 
using namespace std;
const int N=25010;
const int INF=0x3f3f3f;
int n,T;
struct node{
   
    int l,r;
    node(){
   }
    node(int a,int b):l(a),r(b){
   }
    bool operator < (const node t)const{
   
        if(l==t.l) return r<t.r;
        return l<t.l;
    }
}a[N];
struct heap_node{
   
    int id,val;
    heap_node(){
   }
    heap_node(int a,int b):id(a),val(b){
   }
    bool operator <(const heap_node t)const{
   
        return val>t.val;
    }
};
priority_queue<heap_node> heap;
int f[1001000];
int main(){
   
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++){
   
        int l,r;scanf("%d%d",&l,&r);
        a[i]=node(l,r);
    }
    sort(a+1,a+n+1);
    memset(f,INF,sizeof(f));
    f[0]=0;
    heap.push(heap_node(0,0));
    for(int i=1;i<=n;i++){
   
        heap_node tt;
        tt=heap.top();
        while(!heap.empty()&&(heap.top().id<a[i].l-1)){
   
            heap.pop();tt=heap.top();
        }
        if(heap.empty()) break;
        f[a[i].r]=min(tt.val+1,f[a[i].r]);
        heap.push(heap_node(a[i].r,tt.val+1));
    }
    printf("%d",f[T]<INF?f[T]:-1);
    
}

总结:
其实你会发现,数据结构优化其实有点类似于贪心算法,只不过是在进行决策的时候利用了贪心的性质或者数据结构的处理和优化从而减少了得到最优的决策的枚举次数。本身并不是很难想。


单调队列优化

这个优化方式是非常常见的,利用的就是转移方程的单调性来减少转移决策的枚举。用到的工具就是上面提到的双端队列,以及你聪明的大脑。具体使用还是要做题来感受一下啊…

【例题2】Fence(POJ1821

A team of k (1 <= K <= 100) workers should paint a fence which contains N (1 <= N <= 16 000) planks numbered from 1 to N from left to right. Each worker i (1 <= i <= K) should sit in front of the plank Si and he may paint only a compact interval (this means that the planks from the interval should be consecutive). This interval should contain the Si plank. Also a worker should not paint more than Li planks and for each painted plank he should receive Pi $ (1 <= Pi <= 10 000). A plank should be painted by no more than one worker. All the numbers Si should be distinct.

Being the team’s leader you want to determine for each worker the interval that he should paint, knowing that the total income should be maximal. The total income represents the sum of the workers personal income.

Write a program that determines the total maximal income obtained by the K workers.


先按照一般的做法来做,令 F [ i , j ] F[i,j] F[i,j]表示前 i i i个工匠刷前 j j j个木板。

则显然有方程 F [ i , j ] j ≤ S i = m a x { F [ i , j − 1 ] F [ i − 1 , j ] max ⁡ j − L i ≤ k ≤ S i − 1 { F [ i − 1 , k ] + P i ∗ ( j − k ) } F[i,j]_{j\leq S_i}=max\begin{cases} F[i,j-1]\\ F[i-1,j]\\ \max_{j-L_i\leq k\leq S_i-1}\left\{ F[i-1,k]+P_i*(j-k)\right\} \end{cases} F[i,j]jSi=maxF[i,j1]F[i1,j]maxjLikSi1{ F[i1,k]+Pi(jk)}

针对第三个情况,我们发现对于当前层的 i , j i,j i,j P i ∗ j P_i*j Pij为定值,那么可以把第三个式子进行变形
F [ i , j ] = P i ∗ j + m a x { F [ i − 1 , k ] − P i ∗ k } F[i,j]=P_i*j+max\left\{ F[i-1,k]-P_i*k\right\} F[i,j]=Pij+max{ F[i1,k]Pik}
其中枚举条件如上。

那么显然,对于同一个 i i i下的决策 k 1 , k 2 ( k 1 ≤ k 2 ) k_1,k_2(k_1\leq k_2) k1,k2(k1k2),如果存在
F [ i − 1 , k 1 ] − P i ∗ k 1 ≤ F [ i − 1 , k 2 ] − P i ∗ k 2 F[i-1,k_1]-P_i*k_1 \leq F[i-1,k_2]-P_i*k_2 F[i1,k1]Pik1F[i1,k2]Pik2

那么意味着 k 2 k_2 k2的决策必定比 k 1 k_1 k1要好,在当前层的 i i i的情况下, k 2 k_2 k2直接将 k 1 k_1 k1覆盖掉了,那么在转移的时候就直接忽略掉 k 1 k_1 k1

然后就利用双端队列进行维护这个就可以了。

概括一下操作:

  1. 当前层枚举转移的决策,更优的决策将队尾已有的较为差的决策覆盖,即队尾差的全部弹出,再讲当前决策插入进去

  2. 枚举决策转移对象 j j j,由于 j j j也具有单调递增的性质,那么就可以将队首不合法的决策剔除

  3. 队首就是你要找的最优解。

但是很多人并没有把为什么队首就是最优解这个问题讲清楚。

首先可以知道对于原始方程当中的所有决策来说,
F [ i − 1 , k 1 ] − P i ∗ k 1 ≤ F [ i − 1 , k 2 ] − P i ∗ k 2 F[i-1,k_1]-P_i*k_1 \leq F[i-1,k_2]-P_i*k_2 F[i1,k1]Pik1F[i1,k2]Pik2

那么对于一个 F [ i , j ] F[i,j] F[i,j],所转移的对象一定是单个的(有多个也是计算结果相同的,对于答案并无影响),而唯一的一个就是在合法范围内的(队列当中的顺序是递增的,而你在第二步的时候就已经把不合法的去除了),那么这个就是对于当前的 i , j i,j i,j的唯一的转移状态。

然后手动算一下复杂度,你会发现在最外层是枚举的 i i i,内部先做一次状态插入,然后在枚举每一个的状态转移。时间复杂度就是 O ( M N ) O(MN) O(MN)

空间上优化的话就会发现每一个状态之和上一个状态有关,所以滚一下就好了orz。

贴个代码,没有空间优化orz

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MOD=1e9+7;
const int N=16010;
const int M=110;
inline void read(int &x){
   
    x=0;int mm=1; char ch=getchar();
    while((ch>'9'||ch<'0')&&ch!='-') ch=getchar();
    if(ch=='-') mm=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    x=x*mm;
}
inline void read(LL &x){
   
    x
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值