Week11 - 程序设计思维与实践 - 动态规划(二)

必做题

必做题与DP没啥关系,难度为 CSP T1、T2 级别

A - 必做题11-1

题意

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

思路

一年一年地推下去即可,因为是每年增长百分之多少,所有用 double 类型的变量计算。

代码实现

#include 
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
	double n,k,val=200;
	int m;
	scanf("%lf %lf",&n,&k);
	k=0.01*k;
	for(int i=1;i<=20;i++){
		if((double)i*n>=val){
			printf("%d\n",i);
			return 0; 
		}
		val*=(1+k);
	}
	printf("Impossible\n");
	return 0;
}

B - 必做题11-2

题意

蒜头君的班级里有 n 2 n^2 n2个同学,现在全班同学已经排列成一个 n ∗ n n * n nn 的方阵,但是老师却临时给出了一组新的列队方案

为了方便列队,所以老师只关注这个方阵中同学的性别,不看具体的人是谁。这里我们用 0 0 0 表示男生,用 1 1 1 表示女生

现在蒜头君告诉你同学们已经排好的方阵是什么样的,再告诉你老师希望的方阵是什么样的

他想知道同学们已经列好的方阵能否通过顺时针旋转变成老师希望的方阵?

  • 不需要旋转则输出 0

  • 顺时针旋转 90° 则输出 1

  • 顺时针旋转 180° 则输出 2

  • 顺时针旋转 270° 则输出 3

  • 若不满足以上四种情况则输出 −1

  • 若满足多种情况,则输出较小的数字

思路

顺时针旋转其实就是矩阵元素坐标的变换,可以只写一个旋转 90° 的操作,调用多次,不过这里情况比较少,找出坐标变换的规律,直接打表即可。

代码实现

#include 
#include 
#include 
#include 
#include 
using namespace std;

int n,a[30][30],b[30][30],tmp[30][30];

bool Compare(int x[30][30],int y[30][30]){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(x[i][j]!=y[i][j])
				return false;
		}
	}
	return true;
}

void Solve_1(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			tmp[i][j]=a[n+1-j][i];
		}
	}
	if(Compare(tmp,b)){
		printf("1");
		exit(0);
	}
}

void Solve_2(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			tmp[i][j]=a[n+1-i][n+1-j];
		}
	}
	if(Compare(tmp,b)){
		printf("2");
		exit(0);
	}	
}

void Solve_3(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			tmp[i][j]=a[j][n+1-i];
		}
	}
	if(Compare(tmp,b)){
		printf("3");
		exit(0);
	}	
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&b[i][j]);
	if(Compare(a,b)){
		printf("0\n");
		return 0;
	}
	Solve_1();
	Solve_2();
	Solve_3();
	printf("-1\n");
	return 0;	
}

C - 必做题11-3

题意

J u l i u s Julius Julius C a e s a r Caesar Caesar 曾经使用过一种很简单的密码。对于明文中的每个字符,将它用它字母表中后 5 5 5 位对应的字符来代替,这样就得到了密文。比如字符 A A A F F 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 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 ABCDEFGHIJKLMNOPQRSTUVWXYZ

明文 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 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 VWXYZABCDEFGHIJKLMNOPQRSTU

你的任务是对给定的密文进行解密得到明文。

你需要注意的是,密文中出现的字母都是大写字母。密文中也包括非字母的字符,对这些字符不用进行解码。

思路

存储 26 个字母用于密文替换,密文所对应的字符再往前找前 5 个即为明文,分两种情况:

  • 若对应下标小于 5,则下标加 21 替换
  • 否则,下标直接减去 5 替换

代码实现

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

string str;

int main()
{
	getline(cin,str);
	string p="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for(int i=0;i<str.size();i++){
		if(str[i]>='A'&&str[i]<='Z'){
			int t=str[i]-'A';
			if(t<5){
				str[i]=p[t+21];	
			}
			else
				str[i]=p[t-5];
		}	
	}
	cout<<str<<endl;
	return 0;
}

D - 必做题11-4

题意

东东和他的女朋友(幻想的)去寿司店吃晚餐(在梦中),他发现了一个有趣的事情,这家餐厅提供的 n 个的寿司被连续的放置在桌子上 (有序),东东可以选择一段连续的寿司来吃

东东想吃鳗鱼,但是东妹想吃金枪鱼。核 平 起 见,他们想选择一段连续的寿司(这段寿司必须满足金枪鱼的数量等于鳗鱼的数量,且前一半全是一种,后一半全是另外一种)我们用1代表鳗鱼,2代表金枪鱼。

比如,[2,2,2,1,1,1]这段序列是合法的,[1,2,1,2,1,2]是非法的。因为它不满足第二个要求。

东东希望你能帮助他找到最长的一段合法寿司,以便自己能吃饱。

思路

从头遍历输入序列, t m p [ 1 ] tmp[1] tmp[1] 记录当前遍历到的 1 1 1 的个数, t m p [ 2 ] tmp[2] tmp[2] 记录当前遍历到的 2 2 2 的个数(这里的个数指的是某一段连续的1或连续的2的个数并且满足1与2相邻),不断更新这两个变量的值,从 m i n ( t m p [ 1 ] , t m p [ 2 ] ) min(tmp[1],tmp[2]) min(tmp[1],tmp[2]) 中取最大即为最终结果。

代码实现

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn=100010;
int a[maxn],tmp[5],n;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int ans=0,pre=0;
	tmp[1]=tmp[2]=0;
	for(int i=1;i<=n;i++){
		if(a[i]!=pre){
			ans=max(ans,2*min(tmp[1],tmp[2]));
			tmp[a[i]]=1;
			pre=a[i];
		}
		else{
			pre=a[i];
			tmp[pre]++;
		}
	}
	ans=max(ans,2*min(tmp[1],tmp[2]));	
	printf("%d\n",ans);
	return 0;
}

选做

两道背包型DP,一道是多重背包的二进制拆分优化,另一道是背包问题输出路径

E - 选做题11-1 东东与 ATM

题意

一家银行计划安装一台用于提取现金的机器。
机器能够按要求的现金量发送适当的账单。
机器使用正好N种不同的面额钞票,例如D_k,k = 1,2,…,N,并且对于每种面额D_k,机器都有n_k张钞票。
例如,
N = 3,
n_1 = 10,D_1 = 100,
n_2 = 4,D_2 = 50,
n_3 = 5,D_3 = 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

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

Sample Input

735 3 4 125 6 5 3 350
633 4 500 30 6 100 1 5 0 1
735 0
0 3 10 100 10 50 10 10

Sample Output

735
630
0
0

思路

多重背包问题,利用二进制拆分后转化为 0-1背包问题,然后利用滚动数组进行空间优化。

代码实现

#include 
#include 
#include 
#include 
#include 
using namespace std;

int Cash,N,cnt,c[20],w[20],ww[10010],dp[100010];

int main()
{
	while(~scanf("%d%d",&Cash,&N)){
		cnt=0;
		for(int i=1;i<=N;i++){
			scanf("%d%d",&c[i],&w[i]);
			for(int j=1;j<=c[i];j<<=1){
				ww[++cnt]=j*w[i];
				c[i]-=j;
			}
			if(c[i]>0){
				ww[++cnt]=c[i]*w[i];
			}
		}	
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=cnt;i++){
			for(int j=Cash;j>=ww[i];j--){
				dp[j]=max(dp[j],dp[j-ww[i]]+ww[i]);
			}
		}
		printf("%d\n",dp[Cash]);
	}
	return 0;
}

F - 选做题11-2 东东开车了

题意

东东开车出去泡妞(在梦中),车内提供了 n 张CD唱片,已知东东开车的时间是 n 分钟,他该如何去选择唱片去消磨这无聊的时间呢

假设:

  • CD数量不超过20张
  • 没有一张CD唱片超过 N 分钟
  • 每张唱片只能听一次
  • 唱片的播放长度为整数
  • N 也是整数
  • 我们需要找到最能消磨时间的唱片数量,并按使用顺序输出答案(必须是听完唱片,不能有唱片没听完却到了下车时间的情况发生)

本题是 Special Judge

Input

多组输入

每行输入第一个数字N, 代表总时间,第二个数字 M 代表有 M 张唱片,后面紧跟 M 个数字,代表每张唱片的时长 例如样例一: N=5, M=3, 第一张唱片为 1 分钟, 第二张唱片 3 分钟, 第三张 4 分钟

所有数据均满足以下条件:

N≤10000
M≤20

Output

输出所有唱片的时长和总时长,具体输出格式见样例

Sample Input

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

Sample 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

思路

0-1背包问题,不过本题要求输出路径,有两种方法:

  • d p [ M ] [ N ] dp[M][N] dp[M][N] 往前回溯,若 d p [ i ] [ t m p ] = = d p [ i − 1 ] [ t m p ] dp[i][tmp]==dp[i-1][tmp] dp[i][tmp]==dp[i1][tmp] 则说明没选第 i i i 个,否则选了第 i i i
  • 记录一个 p a t h [ i ] [ j ] path[i][j] path[i][j] 数组,在状态转移过程中如果选择了 i i i 就把 p a t h [ i ] [ j ] path[i][j] path[i][j] 置 1 记录选取方案,最后回溯输出路径

代码实现

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int inf=1e9;
int N,M,w[30],path[30],dp[30][10010];

int main()
{
	while(~scanf("%d%d",&N,&M)){
		for(int i=1;i<=M;i++){
			scanf("%d",&w[i]);
		}
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=M;i++){
			for(int j=0;j<=N;j++){
				dp[i][j]=dp[i-1][j];
				if(j-w[i]>=0){
					dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+w[i]);
				}
			}		
		}
		int tmp=N;
		for(int i=M;i>1;i--){
			if(dp[i][tmp]==dp[i-1][tmp])
				path[i]=0;
			else{
				path[i]=1;
				tmp-=w[i];
			}
		}
		path[1]=(dp[1][tmp]>0)?1:0;
		for(int i=1;i<=M;i++){
			if(path[i])
				printf("%d ",w[i]);
		}	
		printf("sum:%d\n",dp[M][N]);
	}
	return 0;
}
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int inf=1e9;
int N,M,w[30],path[30][10010],dp[10010];

int main()
{
	while(~scanf("%d%d",&N,&M)){
		for(int i=1;i<=M;i++){
			scanf("%d",&w[i]);
		}
		memset(dp,0,sizeof(dp));
		memset(path,0,sizeof(path));
		for(int i=1;i<=M;i++){
			for(int j=N;j>=w[i];j--){
				if(dp[j]<dp[j-w[i]]+w[i]){
					dp[j]=dp[j-w[i]]+w[i];
					path[i][j]=1;
				}
			}
		}
		int m=N;
		vector<int> p;
		for(int i=M;i>=1;i--){
			if(path[i][m]){
				p.push_back(w[i]);
				m-=w[i];
			}
		}
		for(int i=p.size()-1;i>=0;i--)
			printf("%d ",p[i]);
		printf("sum:%d\n",dp[N]);
	}
	return 0;
}

你可能感兴趣的:(算法,动态规划)