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
",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
",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
",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
%d",len1,len2);
29     return 0;
30 }

LCIS

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define ll long long 
#define N 3005
int f[N][N],ans;
int n,a[N],b[N];
bool v;
int maxx(int x,int y){
    return x>y?x:y;
}
int main(){
//    freopen("data.txt","r",stdin);
//    freopen("ans.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)scanf("%d",&b[i]);
    for(int i=1;i<=n;i++){
        int val=0;
        if(b[0]<a[i])val=f[i-1][0];
        for(int j=1;j<=n;j++){
            if(a[i]==b[j])f[i][j]=val+1;
            else f[i][j]=f[i-1][j];
            if(b[j]<a[i])val=maxx(val,f[i-1][j]);
            ans=max(ans,f[i][j]);
        }
    }
    printf("%lld
",ans);
    return 0;
} 
LCIS

拓展
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题里也会用到

第一维可以用滚动数组压掉

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long 
#define N 2005
ll f[N],ans;
ll n,a[N],b[N];
ll maxx(ll x,ll y){
    return x>y?x:y;
}
ll minn(ll x,ll y){
    return x>y?y:x;
}
ll ab(ll x,ll y){
    return x>y?x-y:y-x;
}
bool cmp(ll x,ll y){
    return x>y?1:0;
}
ll getmax(){
    sort(b+1,b+1+n);
    for(ll i=1;i<=n;i++)f[i]=ab(a[1],b[i]);
    ll v=0;
    for(ll i=1;i<=n;i++){
        v=f[1];
        for(ll j=1;j<=n;j++){
            v=min(v,f[j]);
            f[j]=v+ab(a[i],b[j]);
        }
    }
    v=f[1];
    for(ll i=1;i<=n;i++)v=minn(v,f[i]);
    return v;
}
ll getmin(){
    sort(b+1,b+1+n,cmp);
    for(ll i=1;i<=n;i++)f[i]=ab(a[1],b[i]);
    ll v=0;
    for(ll i=1;i<=n;i++){
        v=f[1];
        for(ll j=1;j<=n;j++){
            v=min(v,f[j]);
            f[j]=v+ab(a[i],b[j]);
        }
    }
    v=f[1];
    for(ll i=1;i<=n;i++)v=minn(v,f[i]);
    return v; 
}
int main(){
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)scanf("%lld",&a[i]),b[i]=a[i];
    printf("%lld
",minn( getmax(),getmin() ) );
    return 0;
}

f[i][j][k]表示的是前i个人,选了j个,差值为k的 总分数最大值
转移:
1.选i f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k-(p[i]-d[i]])
2.不选i f[i][j][k]=f[i-1][j][k]
可以发现i这个状态是可以 干掉的
下面是详细代码,但有些细节还没搞懂

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<vector>
 5 using namespace std;
 6 
 7 int n,m,cas=1,fix,p,d,S,A,P,D;
 8 int dp[25][805],sub[205],add[205];
 9 vector<int> path[25][805];
10 
11 int main(){
12     while(~scanf("%d%d",&n,&m)){
13         memset(dp,-1,sizeof(dp));
14         for(int i=1;i<=m;++i)
15             for(int j=0;j<805;++j)
16                 path[i][j].clear();
17         fix=20*m;//偏移量,因为可能p[i]-d[i]为负数 
18         dp[0][fix]=0;
19         for(int i=1;i<=n;++i){
20             scanf("%d%d",&p,&d);
21             sub[i]=p-d;
22             add[i]=p+d;
23         }        
24         for(int i=1;i<=n;++i)
25             for(int j=m-1;j>=0;--j)
26                 for(int k=0;k<=2*fix;++k)
27                     if(dp[j][k]>=0)//可以选i 
28                         if(dp[j][k]+add[i]>dp[j+1][k+sub[i]]){//
29                             dp[j+1][k+sub[i]]=dp[j][k]+add[i];
30                             path[j+1][k+sub[i]]=path[j][k];
31                             path[j+1][k+sub[i]].push_back(i);
32                         }
33         int kk;
34         for(kk=0;kk<=fix;++kk)//枚举差值k最小的是几 
35             if(dp[m][fix+kk]>=0||dp[m][fix-kk]>=0)
36                 break;
37         S=dp[m][fix+kk]>dp[m][fix-kk]?fix+kk:fix-kk;
38         A=dp[m][S];        
39         P=(A+(S-fix))/2,D=(A-(S-fix))/2;
40         printf("Jury #%d
",cas++);
41         printf("Best jury has value %d for prosecution and value %d for defence:
",P,D);
42         for(int i=0;i<m;++i)
43             printf(" %d",path[m][S][i]);
44         printf("

");        
45     }
46     return 0;
47 }
陪审团

原文地址:https://www.cnblogs.com/liukx/p/12650289.html