• 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 }
    陪审团

  • 相关阅读:
    如何将平时所学的代码知识,用在实际测试场景中
    CSS,XTHML书写规范以及常见问题总结
    网页底部定位
    时不我待
    用CSS设置Table的细边框的最好用的方法
    CSS Hack总结说明
    批处理轻松删除Win7下WINDOWS.OLD文件夹
    通过iframe调用页面的一部分,实现自己想要的页面
    超简单的javascript
    form在IE6中会影响样式的问题
  • 原文地址:https://www.cnblogs.com/liukx/p/12650289.html
Copyright © 2020-2023  润新知