dp专题练习

Posted henry-1202

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp专题练习相关的知识,希望对你有一定的参考价值。

雾...学初三爷开个坑放一些平时写的dp吧

顺便开另外一篇放一些学过的各种dp

dp总结:https://www.cnblogs.com/henry-1202/p/9194066.html 

开坑先放15道题,后面慢慢补

目标50道题啦~~,目前15/50


 

1.合唱队形

题目链接

LIS模板题,这道题只要正着求一遍LIS,倒着求一遍LIS,然后求max即可,注意因为求了两次LIS,一定会有一个人是被计算了两次的,所以在求max的时候要记得-1

使用O(n2)做法即可

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 110
ll n,a[N],f1[N],f2[N];
int main(){
    read(n);
    for(ll i=1;i<=n;i++)read(a[i]);
    for(ll i=1;i<=n;i++){
        f1[i]=1;
        for(ll j=1;j<i;j++){
            if(a[i]>a[j])f1[i]=max(f1[i],f1[j]+1);
        }
    }
    for(ll i=n;i;i--){
        f2[i]=1;
        for(ll j=i+1;j<=n;j++){
            if(a[i]>a[j])f2[i]=max(f2[i],f2[j]+1);
        }
    }
    ll ans=0;
    for(ll i=1;i<=n;i++)ans=max(ans,f1[i]+f2[i]-1);
    writeln(n-ans);
    return 0;
}
合唱队形

 

2.导弹拦截

题目链接

极其经典的dp题并且极其有趣而且对于初学者来说极其毒瘤,强烈推荐入手

这道题的输入就可以毒死一堆OI初学者了,代码里面会专门讲一下输入的方法

然后洛谷是有加强了一波数据的,需要nlogn解法才能过,这里两种解法都会说

1.O(n2)做法

对于第一问,直接跑一遍LIS就好,不过注意这里是最长下降子序列

对于第二问,可以用Dilworth定理,会在nlogn做法那里讲,这里讲一种贪心做法

因为问的是最少要多少个系统,那么我们肯定要让每个系统拦截尽可能多的导弹啊,所以对于目前已经开了的系统从小到大排序一遍,然后枚举,用目前可以拦截这颗导弹的且可拦截高度最低的系统来拦截(有点绕口,仔细想想)

要排序一遍,所以总复杂度其实是到了O(n2logn)的,不过原题这个复杂度也是可以过的

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 100010
ll f[N],a[N],b[N];
bool vis[N];
int main(){
    ll n=0,m=0;
    while(scanf("%d",&a[++n])==1);n--;
    for(ll i=n;i;i--){
        f[i]=1;
        for(ll j=i+1;j<=n;j++){
            if(a[i]>=a[j]&&f[j]+1>f[i])f[i]=f[j]+1;
        }
        m=max(m,f[i]);
    }
    writeln(m);m=1;b[1]=a[1];
    for(ll i=2;i<=n;i++){
        ll pd=0;
        sort(b,b+m+1);
        for(ll j=1;j<=m;j++){
            if(a[i]<=b[j]){b[j]=a[i];pd=1;break;}
        }
        if(!pd)b[++m]=a[i];
    }
    writeln(m);
    return 0;
}
导弹拦截n^2做法

2.O(nlogn)做法

对于第一问,用LIS的nlogn做法跑一遍最长下降子序列就可以了

对于第二问,就要用Dilworth定理啦,不能用上面的贪心不然会TLE

讲一下Dilworth定理的大概意思:最少的下降序列个数就等于整个序列最长上升子序列的长度

不会证明,上面的链接有证明,虽然我看不懂

所以用nlogn做法跑一遍LIS就好了!

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 100010
ll a[N],f[N],n,m;
int main(){
    while(scanf("%d",&a[++n])==1);
    n--;m=1;
    memset(f,127,sizeof(f));
    f[1]=a[1];
    for(ll i=2;i<=n;i++){
        if(f[m]>=a[i])f[++m]=a[i];
        else {
            ll l=0,r=m;
            while(l<r){
                ll mid=(l+r)>>1;
                if(f[mid]>=a[i])l=mid+1;
                else r=mid;
            }
            f[l]=a[i];
        }
    }
    writeln(m);m=1;
    memset(f,-1,sizeof(f));f[1]=a[1];
    for(ll i=2;i<=n;i++){
        if(f[m]<a[i])f[++m]=a[i];
        else {
            ll l=0,r=m;
            while(l<r){
                ll mid=(l+r)>>1;
                if(f[mid]>=a[i])r=mid;
                else l=mid+1;
            }
            f[l]=a[i];
        }
    }
    writeln(m);
    return 0;
}
导弹拦截nlogn做法

 3.尼克的任务

题目链接

也不知道这算线性dp中的什么类型,看洛谷题解里面kkk说这是资源分配类的dp,姑且就这么认为吧

设f[i]为时间i尼克可以享有的最大空闲时间

很显然每个时间点尼克只会处于两种状态,要么在休息,要么在工作

所以可以得到两个转移方程:

对于这个时间点没有任务的情况:f[i]=f[i-1]+1

对于这个时间点有任务的情况:f[i]=max{f[i],f[i+t]} (t表示当前时间节点的任务的持续时间)

考虑顺推的方法,会发现对于i这个时间点,他的最大时间是和i+t有关的(t表示该任务的持续时间),而在顺推的情况下i+t这个时间点的最大时间是受到i的影响的

显然无法进行dp,推翻顺推解决的想法

于是就想想逆推,没有任务的情况只需要把上面那个转移方程的-1改成+1就好

对于这个时间点有任务的情况,可以直接枚举所有任务,对当前时间点开始做的任务,进行转移

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 100100
ll n,k,f[N],sum[N];
struct node{ll p,t;}t[N];
bool cmp(node a,node b){return a.p>b.p;}
int main(){
    read(n);read(k);
    for(ll i=1;i<=k;i++){read(t[i].p);read(t[i].t);sum[t[i].p]++;}
    for(ll i=n;i;i--){
        if(sum[i]==0)f[i]=f[i+1]+1;
        else {
            for(ll j=1;j<=k;j++){
                if(t[j].p==i)f[i]=max(f[i],f[i+t[j].t]);
            }
        }
    }
    writeln(f[1]);
    return 0;
}
尼克的任务

 4.丝绸之路

题目链接

设f[i][j]为第j天在i城市的最小疲劳值

想一下转移方程就可以出来啦

f[i][j]=min(f[i][j-1],f[i-1][j-1]+d[i]*c[j])

记得初始化一下就好 f[i][i]=f[i-1][i-1]+d[i]*c[i](不休息,直接从头走到尾)

代码

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 1010
ll f[N][N],d[N],c[N],n,m;
//f[i][j]表示第j天在第i个城市的最小疲劳值 
int main(){
    read(n);read(m);
    for(ll i=1;i<=n;i++)read(d[i]);
    for(ll i=1;i<=m;i++)read(c[i]);
    for(ll i=1;i<=n;i++){
        f[i][i]=f[i-1][i-1]+c[i]*d[i];
        for(ll j=i+1;j<=m;j++){
            f[i][j]=min(f[i][j-1],f[i-1][j-1]+d[i]*c[j]);
        }
    }
    writeln(f[n][m]);
    return 0;
}
丝绸之路

5.分队问题

题目链接

首先看到这道题第一想法应该是贪心

这个贪心写法应该也很容易写,排序一遍,然后直接选就可以了

信心满满地提交,会发现WA掉前面两个点

这个贪心想法其实有一个很明显的错误

来一组数据

4

2 3 3 3

贪心做法会输出2,但实际上应该输出1

所以这个贪心的想法是必须cut掉的,因为它没有考虑到分队时剩下来的人的a[i]对于队伍人数的限制

然而这个贪心做法在洛谷能拿80..

顺便说一下这是我校某次测评的题目啊,当时就写了这个贪心,然后挂的挺惨的

那么来想想dp做法

f[i]表示前i个人能够分成的最大的队伍个数

从小到大排序一遍之后,显然可以发现,当第i个人可以加入这个队伍时,当且仅当 i>=a[i]

所以可以得到一个转移方程: f[i]=max(f[k])+1(0<i<=a[i])

但是这样对于百万级别的n是会超时的

考虑怎么去优化它

我们可以开一个数组来储存 f[1...n]的最大值

则转移方程可以改成: f[i]=g[i-a[i]]+1

对于g数组的维护: g[i]=max(g[i-1],f[i])

这样就可以完成O(n)转移

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 1000100
ll n,a[N],f[N],g[N];
int main(){
    read(n);
    for(ll i=1;i<=n;i++)read(a[i]);
    sort(a+1,a+n+1);
    for(ll i=1;i<=n;i++){
        if(i>=a[i])f[i]=g[i-a[i]]+1;
        g[i]=max(f[i],g[i-1]);
    }
    writeln(f[n]);
    return 0;
}
分队问题

6.低价购买

题目链接

第一问直接n2做法求LIS就好

对于第二问,其实是对第一问求出来的f数组进行进一步的dp

题目要求的数列是要去重的,但是放宽一下限制,先考虑不去重的情况

那么就可以得到转移方程:if(f[i]==f[j]+1&&a[i]<a[j])dp[i]+=dp[j]

考虑一下去重情况,如果以i为结尾的数列与以j为结尾的LIS大小相同且a[i]与a[j]是相同的,显然这两个LIS就是重复的

基于此,我们就可以得到去重的方法if(a[i]==a[j]&&f[i]==f[j])dp[i]=0

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 5010
ll a[N],f[N],n,dp[N];
int main(){
    read(n);ll ans1=0;
    for(ll i=1;i<=n;i++)read(a[i]);
    for(ll i=1;i<=n;i++){
        f[i]=1;
        for(ll j=1;j<i;j++){
            if(a[i]<a[j])f[i]=max(f[i],f[j]+1);
        }
        ans1=max(f[i],ans1);
    }
    ll ans2=0;
    for(ll i=1;i<=n;i++){
        if(f[i]==1)dp[i]=1;
        for(ll j=1;j<i;j++){
            if(f[i]==f[j]+1&&a[i]<a[j])dp[i]+=dp[j];
            else if(f[i]==f[j]&&a[i]==a[j])dp[i]=0;
        }
    if(f[i]==ans1)ans2+=dp[i];
    }
    write(ans1);writeln(ans2);
    return 0;
}
低价购买

7.回文字串

题目链接

很巧妙的一道题啊...看到之后是极其懵逼的,完全不知道怎么写,于是悄咪咪地翻了一下学长的博客

get了一个很巧妙的解法:

回忆一下回文串的性质:正着读反着读都是一样的

那么我们可以把原数组倒过来,然后求出两个数组中已经构成"回文"的部分,这里可以使用LCS来求解

然后串的长度减掉LCS的长度就是答案啦

转移方程:

if(s1[i]==s2[j])f[i][j]=f[i-1][j-1]+1

f[i][j]=max(f[i-1][j],f[i][j-1]);

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 1010
char s1[N],s2[N];
ll f[N][N],n;
int main(){
    scanf("%s",s1+1);n=strlen(s1+1);
    for(ll i=1;i<=n;i++)s2[n-i+1]=s1[i];
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=n;j++){
            if(s1[i]==s2[j])f[i][j]=f[i-1][j-1]+1;
            else f[i][j]=max(f[i-1][j],f[i][j-1]);
        }
    }
    writeln(n-f[n][n]);
    return 0;
}
回文字串

 8.[模板]最长公共子序列

题目链接

洛谷的一道模板题,不过这道题其实只有50%的数据算是模板吧,100%的做法很巧妙

因为这道题的两个数列是1~n的全排列,也就是说,这两个数列是没有不同的数字的,只是排列方法不同而已

那么你想到了什么?离散化!

比如输入数据为:

5
3 2 1 4 5
1 2 3 4 5

那么将第一个数列排序,得到他们的大小关系:1-3,2-2,3-1,4-4,5-5

对第二个数列离散:1-3,2-2,3-1,4-4,5-5,也就是说第二个数列被我们搞成了这样:3 2 1 4 5

显然,离散后的第二个数列的LIS就是我们要求的解了

于是只需要在输入过程中把第二个数列离散成第一个数列的排序方式,然后对第二个数列求LIS就好了

求LIS要用O(nlogn)做法

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 100010
ll n,a1[N],a2[N],f[N],map[N];
int main(){
    read(n);
    for(ll i=1;i<=n;i++){read(a1[i]);map[a1[i]]=i;}
    for(ll i=1;i<=n;i++){read(a2[i]);a2[i]=map[a2[i]];}
    memset(f,127,sizeof(f));
    f[0]=0;ll m=0;
    for(ll i=1;i<=n;i++){
        if(a2[i]>f[m])f[++m]=a2[i];
        else {
            ll l=0,r=m;
            while(l<r){
                ll mid=(l+r)>>1;
                if(f[mid]>=a2[i])r=mid;
                else l=mid+1;
            }
            f[l]=a2[i];
        }
    }
    writeln(m);
    return 0;
}
[模板]最长公共子序列

9.魔族密码

题目链接

不是很难的一道dp,排序一遍,然后再hash一下每个字符串

看是不是前缀只需要看hash值是不是相同的就好,如果相同就转移:f[i]=max(f[i],f[j]+1)

这里要排序是因为dp需要满足无后效性,排序之后可以保证第i个字符串之后的字符串一定不会是第i个字符串的前缀(以字符串长度为关键字进行排序)

效率O(n2),因为这题的数据范围不大,如果大的话就最好开一下双hash/三hash/自然溢出hash

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 2010
ll n,f[N],ans=0;
struct node{char ch[76];ll len;}a[N];
#define base 233
#define mod 19260817
#define ull unsigned long long
ull h[N][76];
bool cmp(node a,node b){return a.len<b.len;}
int main(){
    read(n);
    memset(h,0,sizeof(h));
    for(ll i=1;i<=n;i++){
        scanf("%s",a[i].ch+1);
        a[i].len=strlen(a[i].ch+1);
        f[i]=1;
    }
    sort(a+1,a+n+1,cmp);
    for(ll i=1;i<=n;i++)
        for(ll j=1;j<=a[i].len;j++)
            h[i][j]=(h[i][j-1]*base+(ull)(a[i].ch[j]))%mod;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<i;j++){
            if(a[i].ch[1]!=a[j].ch[1])continue;
            if(h[i][a[j].len]==h[j][a[j].len])f[i]=max(f[i],f[j]+1);
        }
        ans=max(ans,f[i]);
    }
    writeln(ans);
    return 0;
}
魔族密码

10.创意吃鱼法

 题目链接

这道题挺好玩的,在做这道题之前推荐先去写一下最大正方形这道题,写完后思路会清晰很多,下面也会放一下最大正方形的AC代码

对于这道题,初始化一下,设l表示i点开始往左的0的个数,r表示i点开始往上的0的个数

设f[i][j]为以(i,j)为右下角能吸到的最多的鱼

那么如果你写过最大正方形这道题,就可以很快的想出转移方程(其实没写过也能想出来):

f[i][j]=min(f[i-1][j-1],min(l[i-1][j],r[i][j-1]))+1;(a[i][j]==1)

然后因为是一个矩形的对角线,而矩形是有两条对角线的,所以还要扫一遍

这个时候f[i][j]为以(i,j)为左下角能吸到的最多的鱼

注意l,r,f都要清零,还有因为是左下角,所以第二次dp是要倒推的

最后的答案就是max{f[i][j]}

技术分享图片
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 2600
ll n,m,a[N][N],f[N][N],ans=0,l[N][N],r[N][N];
int main(){
    read(n);read(m);
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            read(a[i][j]);
            if(!a[i][j])l[i][j]=l[i-1][j]+1,r[i][j]=r[i][j-1]+1;
            else f[i][j]=min(f[i-1][j-1],min(l[i-1][j],r[i][j-1]))+1;
            ans=max(ans,f[i][j]);
        }
    }
    memset(f,0,sizeof(f));
    memset(l,0,sizeof(l));
    memset(r,0,sizeof(r));
    for(ll i=1;i<=n;i++){
        for(ll j=m;j;j--){
            if(!a[i][j])l[i][j]=l[i-1][j]+1,r[i][j]=r[i][j+1]+1;
            else f[i][j]=min(f[i-1][j+1],min(l[i-1][j],r[i][j+1]))+1;
            ans=max(ans,f[i][j]);
        }
    }
    writeln(ans);
    return 0;
}
创意吃鱼法
技术分享图片
#include <cstdio>
#include <cmath>
#include <cstring>
#define ll int
#define inf 1<<30
#define il inline 
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void print(ll x){if(x<0)putchar(-);x=abs(x);if(x>9)print(x/10);putchar(x%10+0);}
il void writeln(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar(
);}
il void write(ll x){if(x<0)putchar(-);x=abs(x);print(x);putchar( );}
using namespace std;
/*===================Header Template=====================*/
#define N 110
ll a[N][N],f[N][N],n,mx=0,m;
int main(){
    read(n);read(m);
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            read(a[i][j]);
            if(a[i][j]==1)f[i][j]=min(min(f[i-1][j-1],f[i-1][j]),f[i][j-1])+1;
            mx=max(f[i][j],mx);
        }
    }
    writeln(mx);
}
最大正方形

 11.UVA10635 Prince and Princess

题目链接

这里先讲下,如果之后有放UVA的题的话都会挂洛谷的链接qwq,不然某些电脑进uva真的慢死(例如我校机房)...

题意:求两个序列的LCS,长度分别为p+1和q+1,极端情况下p,q<=62500,单个序列中的数都不相同

这题的题意其实也很迷... 有点难看出来是求LCS,而且LCS的解法是O(pq)对于可能高达62500的p,q是肯定TLE的,所以想想某些玄学解法

然后就会想起上面的第八题,是的,一模一样,还是离散化,把q序列离散成p序列的排列方式,对离散后的q序列搞一遍O(nlogn)的LIS就可以了

随便找找dp居然还找得到一样的题...

然后就是注意一下二分不要打挂,我二分初始化打挂WA了6次...

技术分享图片
#include <cstdio>
#include <cstring>
#define ll long long
#define inf 1<<30
#define il inline 
#define in1(a) read(a)
#define in2(a,b) in1(a),in1(b)
#define in3(a,b,c) in2(a,b),in1(c)
#define in4(a,b,c,d) in2(a,b),in2(c,d)
il int max(int x,int y){return x>y?x:y;}
il int min(int x,int y){return x<y?x:y;}
il int abs(int x){return x>0?x:-x;}
il void swap(int &x,int &y){int t=x;x=y;y=t;}
il void readl(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void read(int &x){
    x=0;int f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 100010
int a[N],p[N],q[N],n,l1,l2,f[N];
int main(){
    int t;in1(t);
    for(int k=1;k<=t;k++){
        memset(f,127,sizeof(f));
        in3(n,l1,l2);
        for(int i=1;i<=l1+1;i++){int x;in1(x);p[x]=i;}
        for(int i=1;i<=l2+1;i++){int x;in1(x);q[i]=p[x];}
        int m=1;f[1]=q[1];
        for(int i=1;i<=l2+1;i++){
            if(q[i]==0)continue;
            if(q[i]>f[m])f[++m]=q[i];
            else {
                int l=1,r=m;
                while(l<r){
                    int mid=(l+r)>>1;
                    if(f[mid]>q[i])r=mid;
                    else l=mid+1;
                }
                f[l]=q[i];
            }
        }
        printf("Case %d: %d
",k,m);
    }
    return 0;
}
UVA10635

12.木棍加工

题目链接

很水的一道dp,貌似贪心也可以水过去

因为有两个限制条件,所以先把这些木棍按其中一个关键字排序一下,这个关键字就对我们的结果没有什么影响了

在只有一个限制条件的情况下,其实就是求最小的最长下降子序列的个数,于是就想到了上面导弹拦截那道题(第二题)第二问用到的定理

Dilworth定理的大概意思:最少的下降序列个数就等于整个序列最长上升子序列的长度

于是找一遍最长上升子序列就好,因为n<=5000,直接用n2做法就好,如果再大一点就用nlogn做法,这里并不需要

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define inf 1<<30
#define il inline 
#define in1(a) read(a)
#define in2(a,b) in1(a),in1(b)
#define in3(a,b,c) in2(a,b),in1(c)
#define in4(a,b,c,d) in2(a,b),in2(c,d)
il int max(int x,int y){return x>y?x:y;}
il int min(int x,int y){return x<y?x:y;}
il int abs(int x){return x>0?x:-x;}
il void swap(int &x,int &y){int t=x;x=y;y=t;}
il void readl(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void read(int &x){
    x=0;int f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 5010
int n,f[N];
struct node{int w,l;}a[N];
bool cmp(node a,node b){
    if(a.w!=b.w)return a.w>b.w;
    else return a.l>b.l;
}
int main(){
    in1(n);int mx=0;
    for(int i=1;i<=n;i++)in2(a[i].l,a[i].w);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){
        f[i]=1;
        for(int j=1;j<i;j++){
            if(a[i].l>a[j].l)f[i]=max(f[i],f[j]+1);
        }
        mx=max(f[i],mx);
    }
    printf("%d
",mx);
    return 0;
}
木棍加工

 13.[USACO08MAR]跨河River Crossing

题目链接

题目描述有点迷...注意看下面的样例解释啊,语文不好解释不来,就直接放题解了

不难的一道线性dp,设f[i]表示送前i只牛过河的最小时间花费

预处理一下一个c数组表示一次性送i只牛过河的花费

转移方程就不难得出了:f[i]=min(f[i],f[j]+c[i-j])(i>j)

记得初始化f[i]=c[i],还有就是最后输出的时候要减掉m,因为最后一次过河之后john并没有再回去

技术分享图片
#include <cstdio>
#include <cstring>
#define ll long long
#define inf 1<<30
#define il inline 
#define in1(a) read(a)
#define in2(a,b) in1(a),in1(b)
#define in3(a,b,c) in2(a,b),in1(c)
#define in4(a,b,c,d) in2(a,b),in2(c,d)
il int max(int x,int y){return x>y?x:y;}
il int min(int x,int y){return x<y?x:y;}
il int abs(int x){return x>0?x:-x;}
il void swap(int &x,int &y){int t=x;x=y;y=t;}
il void readl(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void read(int &x){
    x=0;int f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 3010
int a[N],c[N],n,m,f[N];
//fi送i头牛过河最小花费 
int main(){
    in2(n,m);
    for(int i=1;i<=n;i++)in1(a[i]);
    c[0]=2*m;
    for(int i=1;i<=n;i++)c[i]=c[i-1]+a[i];
    for(int i=1;i<=n;i++){
        f[i]=c[i];
        for(int j=1;j<i;j++){
            f[i]=min(f[i],f[j]+c[i-j]);
        }
    }
    printf("%d
",f[n]-m);
    return 0;
}
[USACO08MAR]跨河River Crossing

 14.UVA11400 Lighting System Design

题目链接

大概题意是,给你多组数据,对于每组数据,给你n种灯泡,每个灯泡有电压,电源费用,安装费用,需求量四个属性,可以用电压高的灯泡来代替电压低的灯泡以减少电源/安装费用的花费,求你的最低花费

洛谷的翻译其实还是有点迷,不过我讲的好像也有点迷...如果还是不懂题意可以去看紫书,这是里面的一道例题。

设f[i]表示前i种灯泡的最小价值

把它从小到大排序一下,再预处理一个c数组储存前i种灯泡的总需求量

那么f[i]=min(f[i],f[j]+(c[i]-c[j])*a[i].c+a[i].k)

那么答案就是f[n]了,还有就是记得初始化,把f[i]初始化为前i种电灯泡全用这种类型的灯泡所需的价格

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define inf 1<<30
#define il inline 
#define in1(a) read(a)
#define in2(a,b) in1(a),in1(b)
#define in3(a,b,c) in2(a,b),in1(c)
#define in4(a,b,c,d) in2(a,b),in2(c,d)
il int max(int x,int y){return x>y?x:y;}
il int min(int x,int y){return x<y?x:y;}
il int abs(int x){return x>0?x:-x;}
il void swap(int &x,int &y){int t=x;x=y;y=t;}
il void readl(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
il void read(int &x){
    x=0;int f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 1010
int n,f[N],c[N];
//前i种灯的最优价值 
struct node{int v,k,c,l;}a[N];
bool cmp(node a,node b){return a.v<b.v;}
int main(){
    while(scanf("%d",&n)==1&&n){
        for(int i=1;i<=n;i++){
            in4(a[i].v,a[i].k,a[i].c,a[i].l);
        }
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++)c[i]=c[i-1]+a[i].l;
        for(int i=1;i<=n;i++){
            f[i]=a[i].k+a[i].c*c[i];
            for(int j=1;j<i;j++){
                f[i]=min(f[i],f[j]+(c[i]-c[j])*a[i].c+a[i].k);
            }
        }
        printf("%d
",f[n]);
    }
    return 0;
}
uva11400

15.出租车拼车

题目链接

设 f[i][j] 表示 前 i 辆车载了 j 个oier的最优解。

f[i][j]=min(f[i-1][j-x]+x*t[i]+d)  (1≤x≤min(z[i],j))

f[i][j]=f[i-1][j]  (x=0)即如果没有人上车,就不需要付 d 元 

需要注意一下初始化,对于第0辆车,不管怎么载都没办法载(都没有这辆车,但是转移又要用到),所以就初始化f[0][i]=inf

那么答案就是 f[k][n]

技术分享图片
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define il inline 
#define in1(a) read(a)
#define in2(a,b) in1(a);in1(b)
#define in3(a,b,c) in2(a,b);in1(c)
#define in4(a,b,c,d) in2(a,b);in2(c,d)
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 500
int f[N][N],t[N],z[N];
//前i辆车载j个oier 
int n,k,d,s; 
int main(){
    in4(n,k,d,s);ll sum=0;
    for(ll i=1;i<=k;i++){in2(t[i],z[i]);sum+=z[i];}
    if(sum<n){printf("impossible
");return 0;}
    for(ll i=1;i<=n;i++)f[0][i]=inf;
    for(ll i=1;i<=k;i++){
        for(ll j=1;j<=n;j++){
            f[i][j]=inf;
            for(ll k=0;k<=min(z[i],j);k++){
                if(k==0)f[i][j]=f[i-1][j];
                else f[i][j]=min(f[i][j],f[i-1][j-k]+t[i]*k+d);
            }
        }
    }
    printf("%d
",f[k][n]);
    return 0;
}
出租车拼车

15道题搞完啦,现在也已经暑假了,更新速度就会比较快啦

然后对于不同题库的题,如果洛谷有remotejudge我就直接挂洛谷的链接啦

弄个目标,50道题~

好像有点多...努力填啦


 

以上是关于dp专题练习的主要内容,如果未能解决你的问题,请参考以下文章

决策单调性优化dp 专题练习

dp专题练习

poj 动态规划专题练习

[专题练习] Part1 搜索

并不对劲的字符串专题:Trie树

新的练习计划启动~~