SDU程序设计思维Week11-作业
A - 必做题11-1
Description
蒜头君从现在开始工作,年薪 N 万。他希望在蒜厂附近买一套 60 平米的房子,现在价格是 200 万。假设房子价格以每年百分之 K 增长,并且蒜头君未来年薪不变,且不吃不喝,不用交税,每年所得 N 万全都积攒起来,问第几年能够买下这套房子?(第一年年薪 N万,房价 200万)
输入一行,包含两个正整数 N(10≤N≤50),K(1≤K≤20),中间用单个空格隔开。
如果在第 20年或者之前就能买下这套房子,则输出一个整数 M,表示最早需要在第 M 年能买下;否则输出"Impossible"。
输出时每行末尾的多余空格,不影响答案正确性
Sample
Input:
50 10
Output:
8
Idea
题意:已知年薪、房价、房价增长速率,20年内能否买房
迭代20次,累加年薪并更新房价,每次判断当前能否买房即可
Summary
这题很简单,注意第一年的房价是基础房价200W,不用乘以增长率
Codes
#include <iostream>
using namespace std;
float n, k;
float money = 0;
float price = 200;
bool flag = false;
int main()
{
cin >> n >> k;
for (int i = 1; i <= 20; i++) {
money += n;
if (i != 1)price *= (1 + k/100);
if (money >= price) { flag = true; printf("%d\n", i); return 0; }
}
if (!flag)printf("Impossible\n");
}
B - 必做题11-2
Description
蒜头君的班级里有 n^2 个同学,现在全班同学已经排列成一个 n∗n 的方阵,但是老师却临时给出了一组新的列队方案
为了方便列队,所以老师只关注这个方阵中同学的性别,不看具体的人是谁
这里我们用 0 表示男生,用 1 表示女生
现在蒜头君告诉你同学们已经排好的方阵是什么样的,再告诉你老师希望的方阵是什么样的
他想知道同学们已经列好的方阵能否通过顺时针旋转变成老师希望的方阵
1. 不需要旋转则输出 0
2. 顺时针旋转 90° 则输出 1
3. 顺时针旋转 180° 则输出 2
4. 顺时针旋转 270° 则输出 3
若不满足以上四种情况则输出 −1
若满足多种情况,则输出较小的数字
输入第一行为一个整数 n
接下来的 n行同学们已经列好的 01 方阵;
再接下来的 n行表示老师希望的的 01 方阵。
Sample
Input:
4
0 0 0 0
0 0 0 0
0 1 0 0
0 0 0 0
0 0 0 0
0 1 0 0
0 0 0 0
0 0 0 0
Output:
1
Idea
题意:有顺时针旋转90度的操作,判断通过几次操作可以把同学们列好的方阵变成老师希望的方阵
- 实现比较函数,判断两个方阵是否相同
- 实现顺时针旋转90度的操作
旋转后的方阵 i=j0, j=n-i0+1,i0与j0是旋转前方阵的位置 - 分别判断题目所说的四种情况,每种情况是在前一种的基础上顺时针旋转90度
Summary
这题不难,主要是确定好顺时针90度后方阵行列与之前的对应关系
Codes
#include <iostream>
using namespace std;
int n;
int a[25][25], b[25][25],c[25][25];
//判断是否相同
bool judge() {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (a[i][j] != b[i][j]) return false;
return true;
}
//顺时针旋转90
void spin() {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
c[j][n - i + 1] = a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
a[i][j]=c[i][j];
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> b[i][j];
if (judge()) { cout << 0 << endl; return 0; }
spin();
if(judge()) { cout << 1 << endl; return 0; }
spin();
if (judge()) { cout << 2 << endl; return 0; }
spin();
if (judge()) { cout << 3 << endl; return 0; }
cout << -1 << endl;
}
C - 必做题11-3
Description
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
你的任务是对给定的密文进行解密得到明文。
你需要注意的是,密文中出现的字母都是大写字母。密文中也包括非字母的字符,对这些字符不用进行解码。
输入一行密文,密文不为空,而且其中的字符数不超过 200。
输出一行,即密文对应的明文。
输出时每行末尾的多余空格,不影响答案正确性
Sample
Input:
NS BFW, JAJSYX TK NRUTWYFSHJ FWJ YMJ WJXZQY TK YWNANFQ HFZXJX
Output:
IN WAR, EVENTS OF IMPORTANCE ARE THE RESULT OF TRIVIAL CAUSES
Idea
题意:读取一行密文,把它翻译成明文,密文到明文的转变规则是在字母表中向前移5位,保留非字母的字符
对于密文中大写字母转化成它字母表中前5位对应的字符,特别的是ABCDE,在转化成明文时要额外加26,类似轮盘中的移动
Summary
这题不难,第一是密文要读取一行,转化时保留非字母的字符。第二是ABCDE的特别处理。
Codes
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str,ans;
getline(cin, str);
int len = str.size();
for (int i = 0; i < len; i++) {
if (str[i] < 65 || str[i]>90) { ans += str[i]; continue; }
char now;
if (str[i] - 5 < 65)now = str[i] - 5 + 26;
else now = str[i] - 5;
ans += now;
}
cout << ans << endl;
}
D - 必做题11-4
Description
东东和他的女朋友(幻想的)去寿司店吃晚餐(在梦中),他发现了一个有趣的事情,这家餐厅提供的 n 个的寿司被连续的放置在桌子上 (有序),东东可以选择一段连续的寿司来吃
东东想吃鳗鱼,但是东妹想吃金枪鱼。核 平 起 见,他们想选择一段连续的寿司(这段寿司必须满足金枪鱼的数量等于鳗鱼的数量,且前一半全是一种,后一半全是另外一种)我们用1代表鳗鱼,2代表金枪鱼。
比如,[2,2,2,1,1,1]这段序列是合法的,[1,2,1,2,1,2]是非法的。因为它不满足第二个要求。
东东希望你能帮助他找到最长的一段合法寿司,以便自己能吃饱。
输入:
第一行:一个整数n(2≤n≤100000),寿司序列的长度。
第二行:n个整数(每个整数不是1就是2,意义如上所述)
输出:一个整数(代表东东可以选择的最长的一段连续的且合法的寿司)
Sample
Input
7
2 2 2 1 1 2 2
Output
4
Input
6
1 2 1 2 1 2
Output
2
Input
9
2 2 1 1 1 2 2 2 2
Output
6
Idea
题意:在一个数列中寻找最长的1…12…2或2…21…1
边输入边记录当前序列1和2的个数,当现在的数与前一个数不同时num++,相同则在对应的数的个数中加1。
如果出现num=3,即出现类似121的序列意味着这个序列不符合要求,此时需要重置当前这个数对应的个数,重新开始记录一个新的序列,例如从11122到111221,当前的数是1,显然这个序列已经不符合要求,因此我们要把1的个数重置成1,开始记录221…的序列。
由于符合要求的序列1和2的个数要相同,所以结果长度应取1和2的个数的最小值并与自身比较取最大。
Summary
这题需要思考巧妙的方法使时间复杂度降到O(n),注意到序列的长度最大是1e5,如果时间复杂度是O(n^2)会导致超时,所以不能设置两重循环暴力利用滑动窗口改变左右边界来找答案
Codes
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int a[4],ans=0;
int main()
{
cin >> n;
int last = 0, num = 0;
for (int i = 1; i <= n; i++) {
int now;
cin >> now;
if (last != now)num++;
if (num == 3)a[now] = 0, num = 2;
a[now]++;
ans = max(ans, min(a[1], a[2]));
last = now;
}
printf("%d\n", ans * 2);
}
E - 选做题11-1 东东与 ATM
Description
一家银行计划安装一台用于提取现金的机器。
机器能够按要求的现金量发送适当的账单。
机器使用正好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 的程序,可根据具体金额请求机器交付现金。
注意,这个程序计算程序得出的最大现金少于或等于可以根据设备的可用票据供应有效交付的现金。
输入中的每个数据集代表特定交易,其格式为:Cash N n1 D1 n2 D2 … nN DN其中0 <= Cash <= 100000是所请求的现金量,0 <= N <= 10是 纸币面额的数量,0 <= nk <= 1000是Dk面额的可用纸币的数量,1 <= Dk <= 1000,k = 1,N。 输入中的数字之间可以自由出现空格。 输入数据正确。
输出最大可交付的现金
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
Output:
735
630
0
0
Idea
题意:N种面额各有n_k张,组合出小于等于要求的金额的最大交付现金。
多重背包问题
- 二进制拆分
对N种面额拆分,一种面额二进制拆分成logn_k种物品,例如有一种面额有7张,则拆分成001,010,100三种物品,通过这三种物品可以组合成任意0-7张该面额的方案。最终把所有面额的钞票拆分成cnt种物品 - 二进制拆分后转换成01背包问题,利用滚动数组求解
初始化一维的滚动数组实现01背包问题,逆序记录
状态转移方程:
其中wi与vi都是拆分后第i种物品的金额 - 最大交付现金等于以输入金额为索引的滚动数组数值
Summary
这题是多重背包的动态规划问题,基本方法是二进制拆分+01背包。二进制拆分将问题转化成时间复杂度为O(cashx∑logn_i)的01背包问题,滚动数组将空间复杂度降至O(cash),使用滚动数组要逆序遍历
Codes
#include <iostream>
#include <algorithm>
using namespace std;
int cash, n, nk[15], dk[15],w[1020],f[100020];
int main()
{
while (scanf("%d",&cash)!=EOF) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> nk[i] >> dk[i];
}
//二进制拆分
int cnt = 0;
for (int i = 1; i <= n; i++) {
int t = nk[i];
for (int k = 1; k <= t; k <<= 1) {
cnt++;
w[cnt] = k * dk[i];
t -= k;
}
if (t > 0)
{
cnt++;
w[cnt] = t * dk[i];
}
}
//滚动数组 01背包
for (int i = 0; i <= cash; i++)f[i] = 0;
for (int i = 1; i <= cnt; i++)
for (int j = cash; j >= w[i]; j--)
f[j] = max(f[j], f[j - w[i]] + w[i]);
printf("%d\n", f[cash]);
}
}
F - 选做题11-2 东东开车了
Description
东东开车出去泡妞(在梦中),车内提供了 n 张CD唱片,已知东东开车的时间是 n 分钟,他该如何去选择唱片去消磨这无聊的时间呢
假设:
1. CD数量不超过20张
2. 没有一张CD唱片超过 N 分钟
3. 每张唱片只能听一次
4. 唱片的播放长度为整数
5. N 也是整数
我们需要找到最能消磨时间的唱片数量,并按使用顺序输出答案(必须是听完唱片,不能有唱片没听完却到了下车时间的情况发生)
多组输入
每行输入第一个数字N, 代表总时间,第二个数字 M 代表有 M 张唱片,后面紧跟 M 个数字,代表每张唱片的时长 例如样例一: N=5, M=3, 第一张唱片为 1 分钟, 第二张唱片 3 分钟, 第三张 4 分钟
所有数据均满足以下条件:
N≤10000
M≤20
输出所有唱片的时长和总时长,具体输出格式见样例
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
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
Idea
题意:给定一个时间,通过组合n张CD求出小于等于给定时间的最大时间
-
01背包问题
初始化二维数组实现01背包问题,顺序记录
状态转移方程:
其中wi与vi都是第i张CD的播放时间
对于每个子问题,考虑第i个CD的放与不放策略
如果选择不放,那么这个子问题的方案与前i-1件CD放入容量为j的时间段中的方案一样
如果选择放,那么问题转化成前i-1件CD放入容量为j-cd[i]的时间段里
两种策略采用时间更长的策略 -
方案回溯
01背包问题解决后,需要回溯找到最长时间的整个方案,即构成f[m][n]要使用的CD
利用递归回溯整个方案,当未选择第x个cd则此时的方案与f[x-1][y]的方案相同,当选择了第x个CD,则此时的方案与f[x-1][y-cd[x]]的方案相同。
这样回溯到第一张选择的CD后向后逐个输出
Summary
这题是01背包问题+最佳方案的回溯,这里没有再使用一维的滚动数组,使用二维数组方便回溯最佳方案,时间复杂度和空间复杂度都是O(NxM)
Codes
#include <iostream>
#include <algorithm>
using namespace std;
int n, m,cd[25],f[25][10020],pos[25];
void rback(int x, int y) {
if (x <= 0 || y <= 0)return;
if (f[x][y] == f[x - 1][y])rback(x - 1, y); //未选择第x个cd
else if (f[x][y] == f[x - 1][y - cd[x]] + cd[x]) { //选择了第x个cd
rback(x - 1, y - cd[x]);
printf("%d ", cd[x]);
}
}
int main()
{
while(scanf("%d",&n)!=EOF){
//while (cin >> n) {
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> cd[i];
}
for (int i = 0; i <= n; i++)f[0][i] = 0;
for(int i=1;i<=m;i++)
for (int j = 0; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (j - cd[i] >= 0) {
f[i][j] = max(f[i][j], f[i - 1][j - cd[i]] + cd[i]);
}
}
rback(m, n);
printf("sum:%d\n", f[m][n]);
}
}