动态规划之递推dp

Codeforces 429B B. Working out

题意:在 n * m ( 3 ≤ n, m ≤ 1000 ) 的网格中,A从(1,1)点移动到(n,m)点,每次只能向下或者向右移动一格,B从(n,1)点移动到(1,m)点,每次只能向上或者向右移动一格,他们的速度不同,走到一个格子后可以获得相应的权值,当相遇于同一个网格时,他们都不会拿该权值,问 A 与 B 可以拿到的权值之和的最大值。
分析:可以先dp出每个网格到四个角的最大权值,可以枚举每个网格为A与B相遇的格子,因为要使权值最大,所以要使他们相遇的格子尽量少,A与B通过网格的方式如下图,枚举转移之后即为答案。
在这里插入图片描述
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<stack> 
#include<map>
#include<deque>
using namespace std;
typedef long long ll;
#define eps 1e-8
#define pi acos(-1.0)
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F&&(num=-num);
}
const int maxn=1010;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline ll mul(ll a,ll b){
    ll c=a*b-(ll)((long double)a*b/mod+0.5)*mod;
    return c<0?c+mod:c;
}
//void print(__int128 x){
//    if(x<0){
//        putchar('-');
//        x = -x;
//    }
//    if(x>9) print(x/10);
//    putchar(x%10+'0');
//}
ll dp[5][maxn][maxn];
ll a[maxn][maxn];
int main(){
 int n,m;
 read(n);
 read(m);
 for(int i=1;i<=n;i++){
  for(int j=1;j<=m;j++){
   read(a[i][j]);
  }
 }
 //处理出到四角的最大值 
 for(int i=1;i<=n;i++){
  for(int j=1;j<=m;j++){
   dp[1][i][j]=max(dp[1][i][j-1],dp[1][i-1][j])+a[i][j];
  }
  for(int j=m;j>0;j--){
   dp[2][i][j]=max(dp[2][i][j+1],dp[2][i-1][j])+a[i][j];
  }
 }
 for(int i=n;i>0;i--){
  for(int j=1;j<=m;j++){
   dp[3][i][j]=max(dp[3][i][j-1],dp[3][i+1][j])+a[i][j];
  }
  for(int j=m;j>0;j--){
   dp[4][i][j]=max(dp[4][i][j+1],dp[4][i+1][j])+a[i][j];
  }
 }
 //初始化边界 
 for(int i=0;i<=n+1;i++){
  for(int j=1;j<=4;j++){
   dp[j][i][0]=dp[j][i][m+1]=-inf;
  }
 }
 for(int i=0;i<=m+1;i++){
  for(int j=1;j<=4;j++){
   dp[j][0][i]=dp[j][n+1][i]=-inf;
  }
 }
 ll ans=0;
 for(int i=1;i<=n;i++){
  for(int j=1;j<=m;j++){
   ll cnt=0;
   cnt=dp[1][i-1][j]+dp[4][i+1][j]+dp[3][i][j-1]+dp[2][i][j+1];//出入枚举网格的两种方式 
   ans=max(ans,cnt);
   cnt=dp[2][i-1][j]+dp[3][i+1][j]+dp[1][i][j-1]+dp[4][i][j+1];
   ans=max(ans,cnt);
  }
 }
 printf("%lld\n",ans);
} 

UVA 10328 Coin Toss

题意:现有 n 张牌,求出至少有 k 张牌连续位于正面的种数。
分析:当问 ” 至多连续 “ 的数量时,就是典型的dp问题,所以可以简单转换一下,问题可以转换成总数量- 至多有k个连续相同种类数量。
dp[ i ][ j ] 可表示为前 i 张牌连续正面 j 次的数量。因为第 i 张牌有两种情况,所以 dp[ i ][ j ] = dp[ i - 1 ][ j ] * 2,但是把不符合情况的数量也计算进去了。
当 i > j + 1时可能会出现加上第 i 位后出现连续相同的 j + 1 个数的情况,投掷 i - 1 次,连续正面的个数不超过 j 个的话,又要说最后 j 个都为正面,那么第i - j - 1个就一定是反面(对应这类情况而言,并不是说 i - j - 1为反面就一定不符合),那么只要满足前 i - j - 2次中连续正面的个数不超过 j 的话,就是要减掉的那部分。
当 i= j + 1 时,只需要减去一种情况,即出现连续 j + 1 个正面出现。

因为数据过大,应该使用 java 高精度,但是猜测数据没超过 2128,所以使用 __int128 水过去了。
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<stack> 
#include<map>
#include<deque>
using namespace std;
typedef long long ll;
#define eps 1e-8
#define pi acos(-1.0)
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F&&(num=-num);
}
const int maxn=110;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline ll mul(ll a,ll b){
    ll c=a*b-(ll)((long double)a*b/mod+0.5)*mod;
    return c<0?c+mod:c;
}
void print(__int128 x){
    if(x<0){
        putchar('-');
        x = -x;
    }
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
__int128 dp[maxn][maxn];
int main(){
 for(int i=0;i<=100;i++){
  dp[0][i]=1;
  dp[i][0]=1;
  dp[1][i]=2;
 }
 for (int i = 2 ; i <= 100 ; i++){
     for (int j = 1 ; j <= 100 ; j++){
         dp[i][j]=dp[i-1][j]*2;
         if(i==j+1)
    dp[i][j]=dp[i][j]-1;
         else if(i>j+1)
             dp[i][j]=dp[i][j]-dp[i-j-2][j];
     }
 }
 int n,k;
 while(~scanf("%d %d",&n,&k)){
  print(dp[n][n]-dp[n][k-1]);
  puts("");
 }
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值