动态规划专讲

[牛客]被3整除的子序列

题目:

给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除,答案对1e9+7取模

解析:首先对其子问题进行解决,前i个元素所构成的序列有几个子序列mod 3的余数分别是0,1,2,那么推广到i+1,考虑第i+1个元素

1.若单取s[i+1],则dp[i+1][s[i+1]%3]++;

2.不取s[i+1],则余数与前面的子序列相同

3.取前面的子序列以及s[i+1]

下面给出代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int mod=1e9+7;
ll dp[550][3];//表示前i个数中模3余0,1,2的子序列数

int main(){
    string s;
    cin>>s;

    dp[0][(s[0]-'0')%3]=1;
    for(int i=1;i<s.size();i++){
        dp[i][(s[i]-'0')%3]++;//单取
        for(int j=0;j<=2;j++){
            dp[i][j]+=dp[i-1][j]+dp[i-1][((j-(s[i]-'0'))%3+3)%3]//保证是正数;
        }
    }
    cout<<dp[s.size()-1][0]%mod;
    return 0;
}

P1005 [NOIP2007 提高组] 矩阵取数游戏

这里用了__int128 因为不想打高精

首先注意到每一行是相互独立的,那么就分别处理每一行,最后求和

然后找到状态方程dp[i][j]=max(2*dp[i+1][j]+dp[i][i],2*dp[i][j-1]+dp[j][j])

dp[i][j]表示取区间[i,j]所能得到的最大值,因为只能取两边,所以最后一次取的数一定是最边上的两个中的一个,那么就得到了状态方程

注意这里左端点要从n-1开始遍历,因为这样才可以得到前面的状态

最后的dp[1][m]记得×2

#include<bits/stdc++.h>
#define ll __int128
using namespace std;
inline ll read(){
    char ch=getchar();int s=0,w=1;
    while(ch<48||ch>57){
        if(ch=='-')w=-1;ch=getchar();
    }
    while(ch>=48&&ch<=57){
        s=(s<<1)+(s<<3)+ch-48;ch=getchar();
    }
    return s*w;
}
inline void write(ll x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
int n,m;
ll dp[90][90],a[90],ans;
int main(){
    cin>>n>>m;
    while(n--){
        for(int i=1;i<=m;i++)dp[i][i]=read();
        for(int i=m-1;i>=1;i--){
            for(int j=i+1;j<=m;j++){
                dp[i][j]=max(2*dp[i+1][j]+dp[i][i],2*dp[i][j-1]+dp[j][j]);
            }
        }
        ans+=2*dp[1][m];
    }
    write(ans);
    return 0;
}

[洛谷]P3951 [NOIP2017 提高组] 小凯的疑惑 / [蓝桥杯 2013 省] 买不到的数目

这里插入一道非dp的题目,主要是为了下面一道题目的理解

题目:

小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小 凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在小凯无法准确支付的商品。

 ans=a*b-a-b    //证明在下面那道题里,懒得贴了

#include<bits/stdc++.h>
using namespace std;
#define ll long long

inline ll read(){
    char ch=getchar();ll s=0,w=1;
    while(ch<48||ch>57){
        if(ch=='-')w=-1;ch=getchar();
    }
    while(ch>=48&&ch<=57){
        s=(s<<1)+(s<<3)+ch-48;ch=getchar();
    }
    return s*w;
}

inline void write(ll x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
}

int main(){
    ll a,b;
    a=read(),b=read();
    write(a*b-a-b);
}


[洛谷]P1052 [NOIP2005 提高组] 过河

题目:

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,⋯,L(其中L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T)。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度 L,青蛙跳跃的距离范围 S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。

输入格式

第一行有 1 个正整数L(1≤L≤109),表示独木桥的长度。

第二行有 3个正整数 S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数,其中1≤S≤T≤10,1≤M≤100。

第三行有 M个不同的正整数分别表示这 M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。

分析:首先注意到L的取值范围,所以想到压缩路径(我是蒟蒻,偷看了题解)

1.对于S=T的情况,直接分析石头的位置即可

2.对于T>S,考虑最短的两种步长S,S+1:

下证明存在非负整数x,y使\forallk>=0,S*x+(S+1)*y=S^{2}-S+k

(也就是对于距离L>=L1=S^{2}-S的点都可以到达,那么就可以把距离大于L1的两个石头距离缩短为L1-1,不放心可以压缩的路径大一点)

设k=pS+q,p,q均为非负整数,且q<=S-1

S^{2}-S+k= S^{2}-S+pS+q=q(S+1)-qS+ S^{2}-S+pS= q(S+1)+S(S-1+p-q)

那么x=q,y=p+(S-1)-q满足题设(这时两者均非负)

 压缩路径的正确性个人理解:

要证明压缩路径的可行性,就是要证明第二个头后面的棕色部分在压缩前后保持不变,而粉色那段(无论多长)永远由标注数字的绿色那段决定,也就是后面的棕色部分也由绿色那段决定,所以说压缩前后是保持不变的。

#include<bits/stdc++.h>
using namespace std;

inline int read(){
    char ch=getchar();int s=0,w=1;
    while(ch<48||ch>57){
        if(ch=='-')w=-1;ch=getchar();
    }
    while(ch>=48&&ch<=57){
        s=(s<<1)+(s<<3)+ch-48;ch=getchar();
    }
    return s*w;
}

inline void write(int x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
}

int store[110],dp[10000],sto[10000];//记录石头的位置
bool flag[10000]={1};//记录i位置能否跳到
int main(){
    int L,S,T,M;
    L=read(),S=read(),T=read(),M=read();
    for(int i=0;i<M;i++)
        store[i]=read();
    //构造压缩后的桥
    if(S==T){
        int cnt=0;
        for(int i=0;i<M;i++)
            if(store[i]%S==0)++cnt;
        write(cnt);
    }
    
    else {
        sort(store,store+M);
        int x=71,pos=0;//pos记录石头插入的位置
        pos+=min(x,store[0]),sto[pos]=1;
        for(int i=1;i<M;i++){
           pos+=min(x,store[i]-store[i-1]),sto[pos]=1;
        }
        pos+=min(x,L-store[M-1]);

        for(int i=S;i<=pos+T;i++){
            bool check=false;//表示dp[i]没有赋初值
            for(int j=S;j<=T;j++){
                if(flag[i-j]){
                    flag[i]=1;
                    if(check)dp[i]=min(dp[i],dp[i-j]+sto[i]);
                    else dp[i]=dp[i-j]+sto[i],check=true;
                }
            }
        }
        
        int i=0,minn;
        while(!flag[pos+i])++i;//找到可以给dp赋初值的位置
        minn=dp[pos+i];
        while(i<=T){
            if(flag[pos+i])
                minn=min(minn,dp[pos+i]),i++;
        }
        write(minn);
    }
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值