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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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(‘\n‘);}
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\n",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\n",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\n",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\n",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\n");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\n",f[k][n]);
    return 0;
}

出租车拼车



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

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

弄个目标,50道题~

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



dp专题练习

原文地址:https://www.cnblogs.com/henry-1202/p/9211398.html

时间: 2024-07-29 02:56:26

dp专题练习的相关文章

DP专题

DP专题 1. 背包模型 2. 子序列模型 3. 递推DP 4. 区间DP 5. 树形DP 6. 状压DP 学习资料:位操作基础篇之位操作全面总结 如何快速取得一个二进制状态的所有子状态 7. 概率DP 学习资料:简说期望类问题的解法 等等.......

[dp专题]hdu 1160 FatMouse&#39;s Speed

FatMouse's Speed Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 10172    Accepted Submission(s): 4521Special Judge Problem Description FatMouse believes that the fatter a mouse is, the faster i

数位dp 专题

数位dp 专题 先来模板: int dfs(int i,int s,bool e) ///枚举第i位,第i位前一位的数字为s,e表示前缀是否已达到上界.(如果未达到,则枚举0000...~9999..,反之枚举0000...~abcd...) { if(i==-1) return 1; if(!e&&~f[i][s]) return f[i][s];/// f记录的是位数为i,前一数字为s的0000..~9999...的符合条件的个数 int res=0; int u=e?num[i]:9

插头DP专题

建议入门的人先看cd琦的<基于连通性状态压缩的动态规划问题>.事半功倍. 插头DP其实是比较久以前听说的一个东西,当初是水了几道水题,最近打算温习一下,顺便看下能否入门之类. 插头DP建议先理解“插头”的概念.然后会HASH表(这个其实是很基础的东西,应该都会的).然后就是DP. 以及特殊题目的特殊处理. 好像一般是求N,M<=12的网格图的某种回路数或某种通路数的方案数. 大体上每个题说几句特殊处理,有问题请纠正....题目的顺序基本上难度递增 另外代码我都是用括号匹配的.因为感觉连通

区间dp专题练习

区间dp专题练习 题意 1.Equal Sum Partitions ? 这嘛东西,\(n^2\)自己写去 \[\ \] \[\ \] 2.You Are the One 感觉自己智力被吊打 \(dp[i][j]\)表示 , 对于当前的一个空栈 , \(i\)到\(j\)这一段都出栈的最小花费 显然是长得一副区间(诡)dp(异)的样子 , 如何转移呢?(建议自己想想吧) 对于一个\(dp[i][j]\),因为这个\(i\)必须是最先入栈的 , 所以我们可以枚举它的出栈时间\(k\) , 那么总贡

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

决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队列 : 在保证插入和查询的x坐标均具有单调性时可以使用 2.单调栈+二分:保证插入有单调性,不保证查询有单调性 3.分治+ 1 或 2:在每次分治时将\([l,mid]\)这段区间排序后插入,然后更新右区间\([mid+1,r]\)的答案 二.分治.单调队列维护有单调性的转移 (甚至还有分治套分治)

【dp专题】E - Super Jumping! Jumping! Jumping!

E - Super Jumping! Jumping! Jumping! Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description Nowadays, a kind of chess game called “Super Jumping! Jumping! Jumping!” is very popular in HDU. Maybe you are

[Dp专题]F - Piggy-Bank

F - Piggy-Bank Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action

[dp专题]hdu 1260 tickets

Tickets Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 1408    Accepted Submission(s): 687 Problem Description Jesus, what a great movie! Thousands of people are rushing to the cinema. However,