动态规划:WEEK11

必做题:

一.

蒜头君从现在开始工作,年薪 N 万。他希望在蒜厂附近买一套 60 平米的房子,现在价格是 200 万。假设房子价格以每年百分之 K 增长,并且蒜头君未来年薪不变,且不吃不喝,不用交税,每年所得 N 万全都积攒起来,问第几年能够买下这套房子?(第一年年薪 N 万,房价200 万)。

思路

这道题直接模拟就可以,计算每年的工资和房价,如果大于等于就可以,循环20次之后还不行,则不可

代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
 double n0,k,s=200,n=0;
 scanf("%lf%lf",&n0,&k);
 bool f=0;
 k=k/100.0;
 int i=1;
 n = n0;
 for(;i<=20;i++){
  if(s<=n){
   f=1;
   break;
  }
  s=(1+k)*s;
  n+=n0;
 }
 if(f==1) printf("%d",i);
 else printf("Impossible");
 return 0;
} 
二.

蒜头君的班级里有 n 2 n^2 n2个同学,现在全班同学已经排列成一个 n∗n 的方阵,但是老师却临时给出了一组新的列队方案,为了方便列队,所以老师只关注这个方阵中同学的性别,不看具体的人是谁,这里我们用 0 表示男生,用 1 表示女生
现在蒜头君告诉你同学们已经排好的方阵是什么样的,再告诉你老师希望的方阵是什么样的,他想知道同学们已经列好的方阵能否通过顺时针旋转变成老师希望的方阵
不需要旋转则输出 0;
顺时针旋转 90° 则输出 1;
顺时针旋转 180° 则输出 2;
顺时针旋转 270° 则输出 3;
若不满足以上四种情况则输出 -1。
若满足多种情况,则输出较小的数字。

思路

这个题不能站在每一个数字的位置上想问题,而是应该站在行或列的角度想问题,只要把握住一个关键点:一次顺时针旋转,会导致原矩阵的第一行变为新矩阵的第一列(第一个数变为第一行最后一个数),所以,可以把每次旋转的结果记下来,相当于每次的操作都是一样的,只不过每次的数不一样而已。因此最多只需要三次旋转,就能判断。

代码
#include<bits/stdc++.h>
using namespace std;
int n,a[30][30],b[30][30],c[30][30];
bool rotat(){ //每次转,第i列变成第i行 , 第i列中第j个元素,变成第j行中n-i个元素 
 bool f = 1;
 for(int j=0;j<n;j++)
   for(int i=0;i<n;i++){
       c[j][n-1-i] =a[i][j];
       if(c[j][n-1-i]!=b[j][n-1-i]&&f==1) f=0;
   }
 if(f==1) return 1;
    for(int i=0;i<n;i++){
     for(int j=0;j<n;j++){
       a[i][j] = c[i][j];
//       cout<<a[i][j]<<" ";
      }
      
    }
        
    return 0;
}
int main(){
 bool f=1;
 scanf("%d",&n);
 memset(a,0,sizeof a);
 memset(b,0,sizeof b);
 memset(c,0,sizeof c); 
 for(int i=0;i<n;i++)
  for(int j=0;j<n;j++)
    scanf("%d",&a[i][j]);
 for(int i=0;i<n;i++)
   for(int j=0;j<n;j++)
    {
     scanf("%d",&b[i][j]);
     if(a[i][j]!=b[i][j]) f=0;
    }
 if(f==1){
  printf("%d",0);
  return 0;
 }
    // 按照行列的思想来思考
 for(int k=1;k<=3;k++) {
  if(rotat()){
   printf("%d",k);
   return  0 ;
  } 
 }
 printf("%d",-1);
 return 0; 
}

Julius Caesar 曾经使用过一种很简单的密码。对于明文中的每个字符,将它用它字母表中后 5 位对应的字符来代替,这样就得到了密文。比如字符’A’用’F’来代替。如下是密文和明文中字符的对应关系。
密文
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
明文
V W X Y Z A B C D E F G H I J K L M N O P Q R S T U
你的任务是对给定的密文进行解密得到明文。
你需要注意的是,密文中出现的字母都是大写字母。密文中也包括非字母的字符,对这些字符不用进行解码。

思路

这个题也是直接模拟,若字符小于F,则该字母加26再减5,否则,直接减5即可。

代码
#include<bits/stdc++.h>
using namespace std;
string str;
int main()
{
 getline(cin,str);
 for(int i=0;i<str.size();i++){
  if(str[i]>='A'&&str[i]<='Z'){
   if(str[i]<'F') str[i]=str[i]+26; //注意这种处理方法,结合考虑(M1的题) 
   printf("%c",str[i]-5);
  }
  else printf("%c",str[i]);
 }
 return 0;
} 

东东和他的女朋友(幻想的)去寿司店吃晚餐(在梦中),他发现了一个有趣的事情,这家餐厅提供的 n 个的寿司被连续的放置在桌子上 (有序),东东可以选择一段连续的寿司来吃东东想吃鳗鱼,但是东妹想吃金枪鱼。核 平 起 见,他们想选择一段连续的寿司(这段寿司必须满足金枪鱼的数量等于鳗鱼的数量,且前一半全是一种,后一半全是另外一种)我们用1代表鳗鱼,2代表金枪鱼。
比如,[2,2,2,1,1,1]这段序列是合法的,[1,2,1,2,1,2]是非法的。因为它不满足第二个要求。东东希望你能帮助他找到最长的一段合法寿司,以便自己能吃饱

思路

这个题第一反应是个DP,但是后来发现,状态和状态转移方程不好设计,其实这个题没有那么麻烦,只需要分别记录1和2的连续的长度,当出现1…2…1 或者2…1…2这种情况的时候,并统计1序列和2序列的相同数目的长度,并和已有序列长度比较,比较完成之后,1…2…1转换为2…1这中序列,2…1…2转换为1…2序列,在新的基础上统计序列长度

代码
#include<bits/stdc++.h>
using namespace std ;
int n=0,a[109999];
int s1=0,s2=0,maxlen=0;
int main()
{
 memset(a,0,sizeof a);
 int f=0;
 scanf("%d",&n);
 for(int i=0;i<n;i++) scanf("%d",&a[i]);
 for(int i=0;i<n;i++){
  if(a[i]==1){
   if(f==0||f==1) s1++,f=1;
   else if(s1==0) s1++,f=1;
   else if(f==2) {
    int t = min(s1,s2);
    maxlen = max(maxlen,2*t);
    s1=1,f=1; //clear previous 1 ,add 1
   }
  }
  else if(a[i]==2){
   if(f==0||f==2) s2++,f=2;
   else if(s2==0) s2++,f=2;
   else if(f==1){
    int t = min(s1,s2);
    maxlen = max(maxlen,2*t);
    s2=1,f=2; // clear  previous 2 , add 2
   }
  }
  
 }
 int t = min(s1,s2);
 maxlen = max(maxlen,2*t);
 printf("%d",maxlen);
 return 0;
}

选做:多重背包

一家银行计划安装一台用于提取现金的机器,机器能够按要求的现金量发送适当的账单,机器使用正好N种不同的面额钞票,例如 D k D_k Dk,k = 1,2,…,N,并且对于每种面额 D k D_k Dk,机器都有 n k n_k nk张钞票。例如,
N = 3,
n 1 = 10 , D 1 = 100 n_1 = 10,D_1 = 100 n1=10D1=100
n 2 = 4 , D 2 = 50 n_2 = 4,D_2 = 50 n2=4D2=50
n 3 = 5 , D 3 = 10 n_3 = 5,D_3 = 10 n3=5D3=10
表示机器有10张面额为100的钞票、4张面额为50的钞票、5张面额为10的钞票。
东东在写一个 ATM 的程序,可根据具体金额请求机器交付现金。
注意,这个程序计算程序得出的最大现金少于或等于可以根据设备的可用票据供应有效交付的现金。

input

程序输入来自标准输入。 输入中的每个数据集代表特定交易,其格式为:Cash N n1 D1 n2 D2 … nN DN其中0 <= Cash <= 100000是所请求的现金量,0 <= N <= 10是 纸币面额的数量,0 <= nk <= 1000是Dk面额的可用纸币的数量,1 <= Dk <= 1000,k = 1,N。 输入中的数字之间可以自由出现空格。 输入数据正确。

output

对于每组数据,程序将在下一行中将结果打印到单独一行上的标准输出中。

思路

这个题是一个标准的多重背包问题,即有不同的类,每种类型有固定的限制数量,背包的容量就是N,重量和体积都是面值,数量即为货币数量,思路就是先对每一类的数量做二进制拆分,转换为01背包问题,再通过滚动数组(节省内存),得出解。

代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int cash,k;
int v[1000],c[1000],vv[1000],f[100099]; //vv should be large enough
int main()
{
// freopen("in.txt","r",stdin);
    while(~scanf("%d",&cash)){
     memset(v,0,sizeof v);
     memset(c,0,sizeof c);
     memset(vv,0,sizeof vv);
     memset(f,0,sizeof f);
     scanf("%d",&k);
     int  m,n; 
     for(int i=0;i<k;i++){
      scanf("%d%d",&m,&n);
      v[i]=n;
      c[i]=m; //面额n的数量是m 
     }
     int cnt=0;
  for(int i=0;i<k;i++){ //二进制拆分 
   int t = c[i];
   for(int j=1;j<=t;j<<=1){
    cnt++;
    vv[cnt] = j*v[i];
    t-=j; 
   }
   if(t>0){
    cnt++;
    vv[cnt] = t*v[i] ;
   }
  }
  //01背包+滚动数组 
  for(int i=1;i<=cnt;i++){
   for(int j=cash;j>=vv[i];j--){
    f[j]=max(f[j],f[j-vv[i]]+vv[i]);
   } 
  }
  printf("%d\n",f[cash]); 
    }
    return 0;
}

选做:01背包变形

车内提供了N张CD唱片,已知开车的时间是 N分钟,N 是整数
假设:

  1. CD数量不超过20张
  2. 没有一张CD唱片超过 N 分钟
  3. 每张唱片只能听一次
  4. 唱片的播放长度为整数

需要找到最能消磨时间的唱片数量,并按使用顺序输出答案(必须是听完唱片,不能有唱片没听完却到了下车时间的情况发生)

input

每行输入第一个数字N, 代表总时间,第二个数字 M 代表有 M 张唱片,后面紧跟 M 个数字,代表每张唱片的时长 例如样例一: N=5, M=3, 第一张唱片为 1 分钟, 第二张唱片 3 分钟, 第三张 4 分钟,所有数据均满足以下条件:
N≤10000 ,M≤20

5 3 1 3 4

10 4 9 8 4 2

20 4 10 5 7 4

90 8 10 23 1 2 3 4 5 7

45 8 4 10 44 43 12 9 8 2

output

1 4 sum:5

8 2 sum:10

10 5 4 sum:19

10 23 1 2 3 4 5 7 sum:55

4 10 12 9 8 2 sum:45

思路

这个题是个标准的01背包题目,难点在于记录选择的顺序。因为空间足够,所以多使用一个map<int,vector<>>v,记录背包容量小于等于i的时候,选择的CD,在状态转移的时候,根据CD的时间长短,v[i]或者是直接加入新的CD,或者等于v[i-1](相当于将v[i-1]的选的CD全部拿走),最后直接输出v[N]即可

代码
#include<bits/stdc++.h>
using namespace std;
int m=0,n=0;
int a[200];
int f[10099];
map<int,vector<int>>v;
int main()
{
// freopen("in.txt","r",stdin);
 while(~scanf("%d%d",&n,&m)){
  v.clear();
  memset(a,0,sizeof a);
  for(int i=0;i<=n;i++) f[i]=0;
  for(int i=0;i<m;i++) scanf("%d",&a[i]);
  
  for(int i=0;i<m;i++){
   for(int j=n;j>=a[i];j--){
    if(f[j-a[i]]+a[i]>f[j]) {
       v[j] = v[j-a[i]];
       v[j].push_back(a[i]);
    }
    f[j] = max(f[j],f[j-a[i]]+a[i]); 
   }
  }
  for(auto i=v[n].begin();i!=v[n].end();i++){
   printf("%d ",*i);
  }
  printf("sum:%d\n",f[n]);
 }
 return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值