单调队列优化DP能到什么程度

来源:互联网 发布:java键盘监听事件 编辑:程序博客网 时间:2024/04/27 18:46

想了一早上单调队列优化DP,总觉得不能优化到哪里去,又从来没有做过这种需要用单调队列优化的DP,于是自己用手模拟了一下实现过程,瞬间就明白了单调队列优化DP,这个DP的转移方程应该具有的性质。

单调队列很好理解,就是一个双向队列,队首队尾允许删除操作,队尾进行添加操作,维护整个队列的严格单调性,即队列中不存在相等的元素(这样时间常数小一点)。

那么用单调队列优化的DP应该具有怎样的性质呢?

假如我们有下面的DP转移方程:

    f[i] = min( f[j] ) + a[i]

那么当 j 满足一个条件: Low[i] <= j <= Up[i] ,这里的 Low 和 Up 是关于 i 的单调函数,而且是单调递增的,为什么呢?联系经典的单调队列入门题: Sliding Window 想想就清楚了: 当我用下一个 Low[i] 的时候,Low[i] 必须大于等于 Low[i-1] ,因为队首涉及到了要出队列的操作,而队尾的元素上界: Up 也是必须具有单调递增的性质,因为再用队尾的元素的时候,涉及到添加元素的操作。如果还是不很明白,那么联系 Sliding Window 仔细想想这个单调队列删除插入的过程即可。很好想通的。

那么单调队列到底能优化到怎么样的程度呢?我们来看看下面的实验:

【实验方程】:

f[i] = min ( f[j] ) + a[i] ( 1<= j <= i-1 )

【实验过程】:

我做了个测试数据,不大,刚好有 10^5 个数。用传统的 o( n^2 ) 的算法铁定超时,二这个转移方程来看,我们可以不用单调队列优化到 o ( n ) 的复杂度:就是记录一个 1——i 的最小值 Min 每次计算 f[i] 的时候用 Min + a[i] 即可。

那么这道题总共就可以写出三个程序,时间复杂度分别是;

传统的二重循环判定: o( n^2 )

用最小值优化:o( n )

用单调队列优化:未知 (这就是这个实验要探讨的内容)

下面先给出实验程序:

1、传统的二重循环判定:

[cpp] view plaincopy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<algorithm>  
  5. #include<ctime>  
  6. #include<cstdlib>  
  7. #define INF 0x7fffffff  
  8. using namespace std;  
  9. int main()  
  10. {  
  11.     freopen("in.in","r",stdin);  
  12.     freopen("out.out","w",stdout);  
  13.     int n,a[100005],f[100005];  
  14.     scanf("%d",&n);  
  15.     for(int i=1;i<=n;i++)  
  16.         scanf("%d",&a[i]);  
  17.     f[1]=a[1];  
  18.     for(int i=2;i<=n;i++)  
  19.     {  
  20.         int Min=INF;  
  21.         for(int j=1;j<i;j++)  
  22.             if(Min>f[j]) Min=f[j];  
  23.         f[i]=Min+a[i];  
  24.     }  
  25.     for(int i=1;i<=n;i++)  
  26.     {  
  27.         printf("%d",f[i]);  
  28.         if(i==n) printf("\n");  
  29.         else printf(" ");  
  30.     }  
  31.     return 0;  
  32. }  


用最小值优化:

[cpp] view plaincopy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<algorithm>  
  5. #include<ctime>  
  6. #include<cstdlib>  
  7. #define INF 0x7fffffff  
  8. using namespace std;  
  9. int main()  
  10. {  
  11.     freopen("in.in","r",stdin);  
  12.     freopen("out.out","w",stdout);  
  13.     int n,a[100005],f[100005];  
  14.     scanf("%d",&n);  
  15.     for(int i=1;i<=n;i++)  
  16.         scanf("%d",&a[i]);  
  17.     f[1]=a[1];  
  18.     int Min=f[1];  
  19.     for(int i=2;i<=n;i++)  
  20.     {  
  21.         f[i]=Min+a[i];  
  22.         if(f[i]<Min) Min=f[i];  
  23.     }  
  24.     for(int i=1;i<=n;i++)  
  25.     {  
  26.         printf("%d",f[i]);  
  27.         if(i==n) printf("\n");  
  28.         else printf(" ");  
  29.     }  
  30.     return 0;  
  31. }  


用单调队列优化:

[cpp] view plaincopy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<algorithm>  
  5. #include<ctime>  
  6. #include<cstdlib>  
  7. #define INF 0x7fffffff  
  8. using namespace std;  
  9. int main()  
  10. {  
  11.     freopen("in.in","r",stdin);  
  12.     freopen("out.out","w",stdout);  
  13.     int n,a[100005],f[100005];  
  14.     int q[200000],head,tail;  
  15.     scanf("%d",&n);  
  16.     for(int i=1;i<=n;i++)  
  17.         scanf("%d",&a[i]);  
  18.     f[1]=a[1];  
  19.     tail=1; head=0; q[1]=1;  
  20.     for(int i=2;i<=n;i++)  
  21.     {  
  22.         f[i]=f[q[1]]+a[i];  
  23.         //printf("f[q[1]]=%d\n",f[q[1]]);  
  24.         //printf("f[%d]=%d\n",i,f[i]);  
  25.         while(f[i]<=f[q[tail]] && 1<=tail) tail--;  
  26.         q[++tail]=i;  
  27.     }  
  28.     for(int i=1;i<=n;i++)  
  29.     {  
  30.         printf("%d",f[i]);  
  31.         if(i==n) printf("\n");  
  32.         else printf(" ");  
  33.     }  
  34.     return 0;  
  35. }  

0 0