zoukankan      html  css  js  c++  java
  • 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 }
    陪审团

  • 相关阅读:
    gain 基尼系数
    luogu P5826 【模板】子序列自动机 主席树 vector 二分
    牛客挑战赛39 树与异或 离线 树上莫队 树状数组 约数
    4.22 省选模拟赛 三元组 manacher 回文自动机
    4.22 省选模拟赛 最优价值 网络流 最大权闭合子图
    4.18 省选模拟赛 消息传递 树剖 倍增 线段树维护等比数列
    luogu P4008 [NOI2003]文本编辑器 splay 块状链表
    牛客挑战赛39 密码系统 后缀数组
    luogu P1526 [NOI2003]智破连环阵 搜索+最大匹配+剪枝
    luogu P4095 [HEOI2013]Eden 的新背包问题 多重背包 背包的合并
  • 原文地址:https://www.cnblogs.com/liukx/p/12650289.html
Copyright © 2011-2022 走看看