dp线性&&LIS

1.单调栈

相关:

给定序列a[],最少用多少个上升子序列列可以覆盖它?
答案等于a[]的最上不上升子序列的长度

给定序列a[],最少修改多少个位置可以令其变成上升序列
解法:令a_[i] = a[i] - i,对 a_[i] 求最长上升子序列,可以得到最多有多少个位置保持不变
a[ ]1 5 3 2 7
a_[ ]0 3 0 -2 2

对于相邻两个合法的点x,y;设其中间有k个空隙
y-x-1=k
a[y]-a[x]>k
a[y]-a[x]>=k+1
即a[y]-a[x]>=y-x
整理a[y]-y>=a[x]-x

2.LIS

O(n^2)朴素

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 103,INF=0x7f7f7f7f;
int a[maxn],f[maxn];
int n,ans=-INF;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        f[i]=1;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
    for(int i=1;i<=n;i++) 
        ans=max(ans,f[i]);
    printf("%d\n",ans);
    return 0;
}

O(nlogn) 二分插入(贪心)

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=200001;
int a[MAXN],f[MAXN];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    f[1]=a[1];
    int len=1;
    for(int i=2;i<=n;i++)
    {
        if(a[i]>f[len])
            f[++len]=a[i];
        else{
            int j=lower_bound(f+1,f+len+1,a[i])-f;
            f[j]=a[i]; 
        }
    }
    printf("%d\n",len);    
    return 0;
}

树状数组维护最大值

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn =103,INF=0x7f7f7f7f;
struct Node{
    int val,num;
}z[maxn]; 
int T[maxn];
int n;
bool cmp(Node a,Node b)
{
    return a.val==b.val?a.num<b.num:a.val<b.val;
}
void modify(int x,int y)//把val[x]替换为val[x]和y中较大的数 
{
    for(;x<=n;x+=x&(-x)) T[x]=max(T[x],y);
}
int query(int x)//返回val[1]~val[x]中的最大值 
{
    int res=-INF;
    for(;x;x-=x&(-x)) res=max(res,T[x]);
    return res;
}
int main()
{
    int ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&z[i].val);
        z[i].num=i;//记住val[i]的编号,有点类似于离散化的处理,但没有去重 
    }
    sort(z+1,z+n+1,cmp);//以权值为第一关键字从小到大排序 
    for(int i=1;i<=n;i++)//按权值从小到大枚举 
    {
        int maxx=query(z[i].num);//查询编号小于等于num[i]的LIS最大长度
        modify(z[i].num,++maxx);//把长度+1,再去更新前面的LIS长度
        ans=max(ans,maxx);//更新答案
    }
    printf("%d\n",ans);
    return 0;
}

 

例题:导弹拦截

超快写法

求一个不上升序列

求一个上升序列长度

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #define R register
 5 using namespace std;
 6 const int N=100010;
 7 int a[N],d1[N],d2[N],n;
 8 inline bool read(int &x) {
 9     char c=getchar();
10     if(c==EOF)return false;
11     while(c>'9'||c<'0')c=getchar();
12     while(c>='0'&&c<='9') {
13         x=(x<<1)+(x<<3)+(c^48);
14         c=getchar();
15     }
16     return true;
17 }
18 int main() {
19     while(read(a[++n]));n--;
20     R int len1=1,len2=1;
21     d1[1]=d2[1]=a[1];
22     for(R int i=2; i<=n; i++) {
23         if(d1[len1]>=a[i])d1[++len1]=a[i];
24         else *upper_bound(d1+1,d1+1+len1,a[i],greater<int>())=a[i];
25         if(d2[len2]<a[i])d2[++len2]=a[i];
26         else *lower_bound(d2+1,d2+1+len2,a[i])=a[i];
27     }
28     printf("%d\n%d",len1,len2);
29     return 0;
30 }

 

拓展
1. 序列变成一个环(即a[n]和a[1] 相邻)在环上找一个最大的子段?
处理理环上问题时, 一个经典的思路路是“破环成链”
将环复制一份,即令a[i+n] = a[i],那么环上的一段子区间,对应了序列上长度不超过n的一段区间
那么问题转化为找到一个长度不大于 n 的区间,使得 sum[r]-sum[l - 1] 最 大

可行性DP(bool,f[i][j])-->最优化DP(int,f[i]) 

 

有 n 个物品,若 干个询问,每个询问给定 i;问把 物品 i 去掉做背包的结果
维护 f[i] 表示前 i 个物品的背包数组,g[i] 表示后 i 个物品的背包数组,删掉 一个物品,将 f[i - 1] 和 g[i + 1] 合并即可
每次给定区间 [l, r],问把区间 [l, r] 中的物品拿出来做背包的结果
分治, solve(l, r) 表示处理 [l, r] 的 子区间的函数
取中点 mid,预处理 f[i ~ mid] 的背包数组和 g[mid+1 ~ i] 的背包数组,回答经过 mid 的区间的询问
递归处理 (l, mid - 1) 和 (mid + 1, r)

trick

一些问题中,物品大小非常大,开不不下背包数组;另一方面,价值比较小

设 f[i] 表示要获得 i 这么多的价值, 至少要多大的背包
其实是另一个维度入手的背包
这种技巧在其它一些DP题里也会用到

 

上一篇:二、python 中五种常用的数据类型


下一篇:获取元素的方法