dp

dp题单:https://ac.nowcoder.com/acm/problem/collection/576?

0星

滑雪

dfs更简单,按照题意。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int dp[2100][2100],a[2100][2100],m,n,ans=1;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0}; 
int dfs(int x,int y){
    if(dp[x][y]) return dp[x][y];
    else dp[x][y]=1;
    for(int i=0;i<4;i++){
        int fx=x+dx[i];
        int fy=y+dy[i];
        if(fx<1||fx>m||fy<1||fy>n||a[x][y]>=a[fx][fy]) continue;
        dp[x][y]=max(dp[x][y],dfs(fx,fy)+1);
    }
    return dp[x][y];
}
int main(){
    cin>>m>>n;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
        }
    }    
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            ans=max(ans,dfs(i,j));
        }
    }        
    cout<<ans<<endl;
}

 

1星

石子合并

类似区间dp,dp[i][j]的意义为从i到j最优解,跟取数游戏2那个思路上有类似的地方,但是它又多了一步,其实也是暴力。

状态转移方程:dp[j][j+i-1]=min(dp[j][j+i-1],dp[j][k]+dp[k+1][j+i-1]+sum[j][j+i-1]);

sum[i][j]为从i到j的石子和

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[500][500],sum[500][500],a[500],n;
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            sum[i][j]+=sum[i][j-1]+a[j];
        }
    }
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=n;i++) dp[i][i]=0;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=n-i+1;j++){
            for(int k=j;k<j+i-1;k++){
                dp[j][j+i-1]=min(dp[j][j+i-1],dp[j][k]+dp[k+1][j+i-1]+sum[j][j+i-1]);
            //    cout<<dp[j][k]<<' '<<dp[k+1][j+i-1]<<' '<<i<<' '<<j<<' '<<k<<endl;
            }
        }
    }
    /*for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            cout<<dp[i][j]<<' ';
        }
        cout<<endl;
    }*/ 
    cout<<dp[1][n]<<endl;
}

[木]迷雾森林

直接看代码吧

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M=2333; 
int a[3100][3100],dp[3100][3100];
template<class T>inline void read(T &res){
    char c;T flag=1;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
int main(){
    int n,m;
    read(m);read(n);
    for(int i=m-1;i>=0;i--){
        for(int j=0;j<n;j++){
            read(a[i][j]);
        }
    }
    dp[0][0]=1;
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(a[i][j]==1) dp[i][j]=0;
            else{
                if(i-1>=0) dp[i][j]+=dp[i-1][j];
                if(j-1>=0) dp[i][j]+=dp[i][j-1];
            }
            dp[i][j]%=M;
        }
    }
    cout<<dp[m-1][n-1]%M<<endl;
    return 0;
} 

2星

取数游戏2

以前第一次接触dp做过的一道题目,只想出了一半。

它运用了区间dp,dp一般是从小的最优子结构推出大的最优结构,而这个题目则是从区间长度为1开始慢慢推到整个区间,当然每个具有相同长度的小区间都求完之后(也就是遍历)才会去推下一个区间。需要注意的是它是由内向外推,改了好久才发现。

和本题类似的有3星矩阵取数游戏。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[3100],b[3100],dp[3100][3100];
int main(){
    int t,n;
    cin>>t;
    while(t--){
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        memset(dp,0,sizeof(dp));
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++) cin>>b[i];
        for(int i=1;i<=n;i++) dp[i][i]=b[n]*a[i];
        for(int j=1;j<=n;j++){
            for(int i=1;i<=n;i++){
                if(i+j<=n) dp[i][i+j]=max(dp[i+1][i+j]+a[i]*b[n-j],a[i+j]*b[n-j]+dp[i][j+i-1]);
                else break;
    //            cout<<i<<' '<<i+j<<endl;
    //            cout<<dp[i+1][i+j]<<'+'<<a[i]<<'*'<<b[n-j]<<' '<<a[i+j]<<'*'<<b[n-j]<<'+'<<dp[i][j+i-1]<<endl;
            }
        }
        cout<<dp[1][n]<<endl;
    }
} 

传球游戏

这道题不要想太多,直接递推就可以,就是需要注意它是一个圆,还有初始化。

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 int dp[1100][1100];
 6 int main(){
 7     int n,m;
 8     cin>>n>>m;
 9     dp[0][1]=1;
10     for(int i=1;i<=m;i++){
11         for(int j=1;j<=n;j++){
12             if(j==1) dp[i][j]=dp[i-1][2]+dp[i-1][n];
13             else if(j==n) dp[i][j]=dp[i-1][n-1]+dp[i-1][1];
14             else dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
15         }
16     }
17     cout<<dp[m][1]<<endl;
18 }

方格取数

这道题也是很神奇,暴力出奇迹(因为数据比较小),4个for循环。

状态转移方程:dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])))+a[i][j]+a[k][l];

它们的步数一定相同,状态转移要考虑两个人,所以简单的贪心不太可,需要同时考虑两个人(重点),两个人都走一步。

要大胆开多维数组

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[21][21][21][21],a[2100][2100],pre[1100];
int main(){
    int n,x,y,w;
    cin>>n;
    while(scanf("%d%d%d",&x,&y,&w)){
        if(x==0&&y==0&&w==0) break;
        a[x][y]=w;
    }
//    pre[1]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                for(int l=1;l<=n;l++){
                    if(i+j!=k+l) continue; 
                    dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])))+a[i][j]+a[k][l];
                    if(l==j&&k==i) dp[i][j][k][l]-=a[i][j];
                }
            }
        }
    }
    cout<<dp[n][n][n][n]<<endl;
    
}

CSL分苹果

背包问题,就是在不超过sum/2的情况下,尽可能取多的苹果。与01背包相比,01背包的空间=苹果的质量,01背包的价值也=苹果的质量

状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[11100],dp[11000][11000];
int main(){
    int n,sum=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=sum/2;j++){
            dp[i][j]=dp[i-1][j];
            if(j-a[i]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
        }
    }
    cout<<dp[n][sum/2]<<' '<<sum-dp[n][sum/2]<<endl;
}

Rabbit的工作(1)

这道题主要是在dp数组开的意义,dp[i][j][k]表示第i天工作j天此时连续工作k天,但是多开一维,容易超内存,滚动循环。

只要数组开出来之后 ,就应该会推了,状态转移方程:

if(s[i]=='1'){dp[j][0]=max(dp[j][0],dp[j][k]);dp[j][k]=min(dp[j][k],dp[j-1][k-1]+k);}//两种情况,休息或者继续工作

else dp[j][0]=max(dp[j][0],dp[j][k]);//只能休息,需要注意的是dp[j][0]=min(dp[j][x])x属于[0,j]

建议自己再写一个三维的更好理解。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char s[1100];
int dp[1100][1100];
int main(){
    int n,K;
    cin>>n>>K;
    cin>>s+1;
    memset(dp,0x3f3f3f3f,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=i;j;j--){
            for(int k=1;k<=j;k++){
                dp[j][0]=min(dp[j][0],dp[j][k]);
                if(s[i]=='1'){
                    dp[j][k]=min(dp[j][k],dp[j-1][k-1]+k); 
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=i;j++){
            if(dp[i][j]<=K) ans=max(ans,i);
        }
    }
    cout<<ans<<endl;
    return 0;
} 

数字三角形

看代码就好

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main(){
    int n,cnt=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            printf("%4d",++cnt);
        }
        cout<<endl;
    }
    return 0;
} 

小A买彩票

为数不多自己写出的题目,也是按照题意来即可。

dp[i][j]的含义为当前选择为第i次得分为j时,所总共有的方法数目。

状态转移方程:if(j>=1) dp[i][j]+=dp[i-1][j-1];

                         if(j>=2) dp[i][j]+=dp[i-1][j-2];
                         if(j>=3) dp[i][j]+=dp[i-1][j-3];
                         if(j>=4) dp[i][j]+=dp[i-1][j-4];

最后总的方法数与可能的方法数gcd即为结果。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll dp[37][3100];
ll gcd(ll a,ll b){
    return  b==0?a:gcd(b,a%b);
}
int main(){
    int n;
    cin>>n;
    if(n==1){
        cout<<"1/2"<<endl;
        return 0;
    }else if(n==0){
        cout<<"1/1"<<endl;
        return 0;
    } 
    dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i*4;j++){
            if(j>=1) dp[i][j]+=dp[i-1][j-1];
            if(j>=2) dp[i][j]+=dp[i-1][j-2];
            if(j>=3) dp[i][j]+=dp[i-1][j-3];
            if(j>=4) dp[i][j]+=dp[i-1][j-4];
        }
    }
    ll ans=0; 
    for(int i=3*n;i<=4*n;i++){
        ans+=dp[n][i]; 
    }
    ll k=(ll)1;
    for(int i=1;i<=n;i++){
        k*=4;
    }
    cout<<ans/gcd(k,ans)<<"/"<<k/gcd(k,ans)<<endl;
    return 0;
} 

三星

合并回文子串

经典的区间dp,利用小的最优子区间去推大的最优子区间,最终达到整个区间。

dp[r1][l1][r2][l2]表示的是字符串1从r1到l1,字符串2从r2到l2是否能够构成一个回文子串,利用丨(或)运算较为方便。

状态转移方程:if(i+j<=1) dp[r1][l1][r2][l2]=1;

                         else{
                                dp[r1][l1][r2][l2]=0;
                                if(a[r1]==a[l1]&&i>1) dp[r1][l1][r2][l2]|=dp[r1+1][l1-1][r2][l2];
                                if(a[r1]==b[l2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1+1][l1][r2][l2-1];
                                if(b[r2]==b[l2]&&j>1) dp[r1][l1][r2][l2]|=dp[r1][l1][r2+1][l2-1];
                                if(a[l1]==b[r2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1][l1-1][r2+1][l2];
                               }

每次判断完再比较与ans的值;当然其中也有很多细节,不多赘述。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[60][60][60][60]; 
int main(){
    int n;
    cin>>n;
    while(n--){
        char a[311],b[311];
        cin>>a+1>>b+1;
        memset(dp,0,sizeof(dp));
        int len1=strlen(a+1),len2=strlen(b+1),ans=0;
        for(int i=0;i<=len1;i++){
            for(int j=0;j<=len2;j++){
                for(int r1=1,l1=i;l1<=len1;r1++,l1++){
                    for(int r2=1,l2=j;l2<=len2;r2++,l2++){
                        if(i+j<=1) dp[r1][l1][r2][l2]=1;
                        else{
                            dp[r1][l1][r2][l2]=0;
                            if(a[r1]==a[l1]&&i>1)  dp[r1][l1][r2][l2]|=dp[r1+1][l1-1][r2][l2];
                            if(a[r1]==b[l2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1+1][l1][r2][l2-1];
                            if(b[r2]==b[l2]&&j>1)  dp[r1][l1][r2][l2]|=dp[r1][l1][r2+1][l2-1];
                            if(a[l1]==b[r2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1][l1-1][r2+1][l2];
                        }
                        if(dp[r1][l1][r2][l2]) ans=max(ans,i+j);
                    }
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
} 

Butterfly

这道题做起来更像一道暴力题,并不需要dp

首先设了正上,左上,右上,左下,右下,各自方向连续X数目的多少zt[2100][2100],zs[2100][2100],ys[2100][2100],zx[2100][2100],yx[2100][2100];

然后进行遍历判断,找最大的蝴蝶翅膀的数目。

当然蝴蝶的中心可以是O所以也需要判断,需要注意这个细节问题。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
char s[2100][2100];
int zt[2100][2100],zs[2100][2100],ys[2100][2100],zx[2100][2100],yx[2100][2100];
int main(){
    int n,m,tt=0;
    cin>>m>>n;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            cin>>s[i][j];
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(s[i][j]=='X'){
                zs[i][j]=zs[i-1][j-1]+1;
                zt[i][j]=zt[i-1][j]+1;
                tt=max(tt,zt[i][j]);
            }
        }
    }
    for(int i=m;i>=1;i--){
        for(int j=n;j>=1;j--){
            if(s[i][j]=='X'){
                yx[i][j]=yx[i+1][j+1]+1;
            }
        }
    }
    for(int i=m;i>=1;i--){
        for(int j=1;j<=n;j++){
            if(s[i][j]=='X'){
                zx[i][j]=zx[i+1][j-1]+1; 
            }
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=1;j--){
            if(s[i][j]=='X'){
                ys[i][j]=ys[i-1][j+1]+1;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            int t=min(min(ys[i][j],yx[i][j]),min(zs[i][j],zx[i][j]));
            if(s[i][j]=='X'){
                for(int k=min(t,tt);k>=ans;k--){
                    if(zt[i+k-1][j-k+1]>=2*k-1&&zt[i+k-1][j+k-1]>=2*k-1){
                        ans=k;break;
                    }
                }
            }
            else{
                if(i-1<1||i+1>m||j-1<1||j+1>n) continue;
                int kong=min(min(zs[i-1][j-1],ys[i-1][j+1]),min(zx[i+1][j-1],yx[i+1][j+1]));
                for(int k=min(kong,tt);k>=ans;k--){
                    if(zt[i+k-1][j-k+1]>=2*k-1&&zt[i+k-1][j+k-1]>=2*k-1){
                        ans=k;break;
                    }
                }
            }
        }
    }
    cout<<max(0,ans*2-1)<<endl;
}
/*
5 6
OOOOXX
XXXXOO
OXOXOX
XOXOXO
XOXOXO
*/ 

矩阵取数游戏

又到了我们的取数游戏了,这个题跟上边的取数游戏2几乎一样,不过输入输出有些大,有我自己写的dp,和从别的地方偷的输入输出(狗头保命)

输入输出代码:

inline __int128_t read()
{
    __int128_t x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return f*x;
}
void print(__int128_t x)
{
    if(x < 0) {putchar('-');x = -x;}
    if(x/10) print(x/10);
    putchar(x%10+'0');
}

状态转移方程:dp[i][k][k+j]=max(dp[i][k+1][k+j]+a[i][k]*f[m-j],dp[i][k][k+j-1]+a[i][j+k]*f[m-j]);

AC代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef __int128_t ll;
ll f[100],dp[100][100][100],a[100][100];
inline __int128_t read()
{
    __int128_t x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return f*x;
}
void print(__int128_t x)
{
    if(x < 0) {putchar('-');x = -x;}
    if(x/10) print(x/10);
    putchar(x%10+'0');
}
void get(){
    f[0]=1; 
    for(int i=1;i<=90;i++){
        f[i]=f[i-1]*2;
    }
}
int main(){
    ll n,m,ans=0;
    n=read();m=read();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            a[i][j]=read();
        }
    }
    get();
    /*for(int i=1;i<=80;i++){
        cout<<f[i]<<' '<<i<<endl;
    }*/
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dp[i][j][j]=a[i][j]*f[m];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int k=1;k<=m;k++){
                if(j+k<=m) dp[i][k][k+j]=max(dp[i][k+1][k+j]+a[i][k]*f[m-j],dp[i][k][k+j-1]+a[i][j+k]*f[m-j]);
                else break;
            }
        }
        ans+=dp[i][1][m];
    }
    print(ans);
} 

 

采药

01背包裸的板子

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[4000][3100],a[3100],w[3100];
int main(){
    int t,m;
    cin>>t>>m;
    for(int i=1;i<=m;i++){
        cin>>a[i]>>w[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=0;j<=t;j++){
            dp[i][j]=dp[i-1][j];
            if(j-a[i]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+w[i]);
        }
    }
    /*for(int i=1;i<=m;i++){
        for(int j=0;j<=t;j++){
            cout<<dp[i][j]<<' ';
        } 
        cout<<endl;
    }*/
    cout<<dp[m][t]<<endl;
} 

过河

这道题难在离散化,而不是dp(一看数据显然需要离散化1e9),当然也有不好好读题的小朋友(包括我),求踩石子最少的数目(细节)。

它可以到达大于90(也可以71,洛谷上有以为位大佬的详细证明,当然我们也可以猜的大于某个数)的任意距离,所以一但距离大于这个数我们可以令它等于90。

离散化代码:

for(int i=1;i<=m;i++){
        ll dis=a[i]-a[i-1];
        if(dis>=90) dis=90;
        b[i]=b[i-1]+dis;
        vis[b[i]]=1; 
    }

当然如果s==t,直接进行判读,代码:

if(s==t){
        ll cnt=0;
        for(int i=1;i<=m;i++){
            if(a[i]%s==0) cnt++;
        }
        cout<<cnt<<endl;
        return 0;
    }

之后就是距离外循环,步数内循环,dp了

也得防止跳过最后一个石子后可以直接跳出,代码:

    for(ll i=b[m];i<=l;i++) ans=min(ans,dp[i]);

AC代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
ll a[2100],vis[310000],dp[310000],b[2100]; 
int main(){
    ll l,s,t,m;
    cin>>l>>s>>t>>m;
    for(int i=1;i<=m;i++) cin>>a[i];
    sort(a+1,a+m+1);
    if(s==t){
        ll cnt=0;
        for(int i=1;i<=m;i++){
            if(a[i]%s==0) cnt++;
        }
        cout<<cnt<<endl;
        return 0;
    }
    for(int i=1;i<=m;i++){
        ll dis=a[i]-a[i-1];
        if(dis>=90) dis=90;
        b[i]=b[i-1]+dis;
        vis[b[i]]=1; 
    }
    l=b[m]+90;
    memset(dp,INF,sizeof(dp));
    dp[0]=0;
    for(ll i=1;i<=l;i++){
        for(ll j=s;j<=t;j++){
            if(i-j>=0){
               //if(i==2) cout<<dp[i]<<endl;
               if(vis[i]) dp[i]=min(dp[i],dp[i-j]+1);
               else dp[i]=min(dp[i],dp[i-j]);
            }
        }
    }
    ll ans=INF;
    for(ll i=b[m];i<=l;i++) ans=min(ans,dp[i]);
    //for(ll i=1;i<=l;i++) cout<<dp[i]<<' '<<i<<endl;
    cout<<ans<<endl;
} 

合唱队形

曾经做到过的原题

求两个最长递增(递减)子序列,dp[i]表示到i的最长子序列,dp1[i]表示大于i的最长递减序列,总人数减去两者相加最大值即为结果。

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int dp[2100],dp1[2100],a[2100];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        dp[i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=n;j>i;j--){
            if(a[i]>a[j]) dp1[i]=max(dp1[i],dp1[j]+1);
        }
    }
    int ans=0;
    //cout<<dp[4]<<' '<<dp[6]<<endl;
    for(int i=1;i<=n;i++){
        ans=max(ans,dp[i]+dp1[i]);
    }
    cout<<n-ans<<endl;
} 

开心的金明

01背包的裸板子

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[32][31000],a[32],w[32];
int main(){
    int N,m;
    cin>>N>>m;
    for(int i=1;i<=m;i++) cin>>a[i]>>w[i];
    for(int i=1;i<=m;i++){
        for(int j=0;j<=N;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=a[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]*w[i]);
        }
    }
    cout<<dp[m][N]<<endl;
}

金明的预算方案

有依赖的背包,注意每个主件只会有两个附件,所以总共会有五种情况:

1,不选         2,只选主件       3,选主件和附件A       4,选主件和附件B      5,选主件和附件A,B

主附件的绑定代码:

for(int i=1;i<=m;i++){
        if(q[i]){
            vis[q[i]][++b[q[i]]]=i;
        }
    }

vis[i][1]和vis[i][2]表示主件 i 附件的位置(如果存在的话)

之后是分组背包的感觉,代码:

if(j-v[i]>=0) dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]);
if(t1&&j-v[i]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[t1]-v[i]]+v[t1]*p[t1]+v[i]*p[i]);
if(t2&&j-v[i]-v[t2]>=0) dp[j]=max(dp[j],dp[j-v[t2]-v[i]]+v[t2]*p[t2]+v[i]*p[i]);
if(t1&&t2&&j-v[i]-v[t2]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[i]-v[t1]-v[t2]]+v[i]*p[i]+v[t1]*p[t1]+v[t2]*p[t2]);

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int vis[100][5],dp[36000],b[100],v[100],p[100],q[100];
int main(){
    int N,m;
    cin>>N>>m;
    for(int i=1;i<=m;i++){
        cin>>v[i]>>p[i]>>q[i];
    }
    for(int i=1;i<=m;i++){
        if(q[i]){
            vis[q[i]][++b[q[i]]]=i;
        }
    }
    for(int i=1;i<=m;i++){
        if(!q[i]){
            int t1=vis[i][1],t2=vis[i][2];
            for(int j=N;j>=0;j--){
                if(j-v[i]>=0) dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]);
                if(t1&&j-v[i]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[t1]-v[i]]+v[t1]*p[t1]+v[i]*p[i]);
                if(t2&&j-v[i]-v[t2]>=0) dp[j]=max(dp[j],dp[j-v[t2]-v[i]]+v[t2]*p[t2]+v[i]*p[i]);
                if(t1&&t2&&j-v[i]-v[t2]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[i]-v[t1]-v[t2]]+v[i]*p[i]+v[t1]*p[t1]+v[t2]*p[t2]);
            }
        }
    }
    cout<<dp[N]<<endl;
    return 0;
} 

过河卒

dp入门题,需要注意障碍物

障碍物代码:

flag[x][y]=1;
if(x>=1&&y>=2) flag[x-1][y-2]=1;if(x>=2&&y>=1) flag[x-2][y-1]=1;
if(x>=1&&y+2<=m) flag[x-1][y+2]=1;if(x>=2&&y+1<=m) flag[x-2][y+1]=1;
if(x+1<=n&&y+2<=m) flag[x+1][y+2]=1;if(x+2<=n&&y+1<=m) flag[x+2][y+1]=1;
if(x+1<=n&&y>=2) flag[x+1][y-2]=1;if(x+2<=n&&y>=1) flag[x+2][y-1]=1;

遇到障碍物则方法数为零

AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll dp[31][31],flag[31][31];
int main(){
    ll n,m,x,y;
    cin>>n>>m>>x>>y;
    dp[0][0]=1;
    flag[x][y]=1;
    if(x>=1&&y>=2) flag[x-1][y-2]=1;if(x>=2&&y>=1) flag[x-2][y-1]=1;
    if(x>=1&&y+2<=m) flag[x-1][y+2]=1;if(x>=2&&y+1<=m) flag[x-2][y+1]=1;
    if(x+1<=n&&y+2<=m) flag[x+1][y+2]=1;if(x+2<=n&&y+1<=m) flag[x+2][y+1]=1;
    if(x+1<=n&&y>=2) flag[x+1][y-2]=1;if(x+2<=n&&y>=1) flag[x+2][y-1]=1;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            if(flag[i][j]){
                dp[i][j]=0;
                continue;
            }
            if(i>=1) dp[i][j]+=dp[i-1][j];
            if(j>=1) dp[i][j]+=dp[i][j-1];
        }
    }
    cout<<dp[n][m]<<endl;
} 

拦截导弹

以前做过的训练题

首先求一个最长递减子串,然后用一个储备数组来储备出现的系统接下来能接受的最大高度

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int dp[310000],dp1[310000],a[310000];
int main(){
    int x,n=0,cnt=1;
    while(scanf("%d",&x)!=EOF){
        a[n++]=x;
    }
    dp[0]=a[0];
    for(int i=0;i<=n;i++) dp1[i]=INF;
    for(int i=1;i<n;i++){
        int t=0;
        for(int j=0;j<cnt;j++){
            if(a[i]<dp[j]){
                dp[j]=a[i];
                t=1;break;
            }
        }
        if(!t){
            dp[cnt++]=a[i];
        }
    }
    for(int i=n-1;i>=0;i--){
        *lower_bound(dp1,dp1+n,a[i])=a[i];
    }
    cout<<lower_bound(dp1,dp1+n,INF)-dp1<<endl<<cnt<<endl;
}

简单瞎搞题

bitset秀到我了,初始是会有几个1的存在,然后每次加数左移很所加数的位数(就是加几个零)然后每次丨运算如果两种方式所加出来的数相同,那么它肯定在所对应的相同的位数上是1。

最后数1,ans.count().

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
const int M=1e6+5;
int main(){
    int n;
    cin>>n;
    bitset<M>ans;
    ans[0]=1;
    while(n--){
        int r,l;
        cin>>r>>l;
        bitset<M>temp;
        for(int i=r;i<=l;i++) temp|=ans<<i*i;
        ans=temp;
    }
    cout<<ans.count()<<endl;
    return 0;
}

花店橱窗

把给出的数据看成一个图,起点为第一行的任意位置,终点为最后一行的任意位置(有一定的限制),只能向下同时向右走,找最大值。

然后就是我之前写过的的链表方法记录走到最大值的路径。

循环中记录每次记录路径的代码:

vis[(i-1)*v+j]=(i-2)*v+k

寻找真正的最大值路径代码:

int ans=dp[f][v],t=f*v;
for(int i=1;i<v;i++){
    if(ans<dp[f][i]){
       ans=dp[f][i];
       t=(f-1)*v+i;
    }
}

因为是倒序的所以重新记录每个位置的代码:

while(vis[t]!=t){
        c[cnt++]=(t%v==0?v:t%v);
        t=vis[t];
    }

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int vis[4100],a[310][310],dp[310][310],c[110];
int main(){
    int f,v;
    cin>>f>>v;
    for(int i=1;i<=f;i++){
        for(int j=1;j<=v;j++){
            cin>>a[i][j];
        }
    }
    memset(dp,-0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=v;i++){
        dp[1][i]=a[1][i];
        vis[i]=i;
    }
    for(int i=2;i<=f;i++){
        for(int j=1;j<=v;j++){
            for(int k=1;k<j;k++){
                if(dp[i][j]<dp[i-1][k]+a[i][j]){
                    dp[i][j]=dp[i-1][k]+a[i][j];
                    vis[(i-1)*v+j]=(i-2)*v+k;
                }
            }
        }
    }
    int ans=dp[f][v],t=f*v;
    for(int i=1;i<v;i++){
        if(ans<dp[f][i]){
            ans=dp[f][i];
            t=(f-1)*v+i;
        }
    }
    cout<<ans<<endl;
    int cnt=1;
    while(vis[t]!=t){
        c[cnt++]=(t%v==0?v:t%v);
        t=vis[t];
    }
    cout<<t<<' ';
    for(int i=cnt-1;i>=1;i--) cout<<c[i]<<' ';
    cout<<endl;
} 

总结:区间dp的题目:取数游戏2,矩阵取数游戏,合并回文字符串,石子合并

01背包的灵活运用:CSL分苹果

有依赖的背包:金明的预算方案

多维dp:方格取数

直接递推:小A的彩票,传球游戏

有思维含量的dp数组设置:Rabbit的工作(1)

离散化:过河

拼接结论:拦截导弹,合唱队列

链表记录路径:花店橱窗

混入的dfs:滑雪

 

posted @ 2020-08-12 15:38  清水仙  阅读(157)  评论(0编辑  收藏  举报