SDU程序设计思维Week12-作业
A - 必做题 - 1
Description
给出n个数,zjm想找出出现至少(n+1)/2次的数, 现在需要你帮忙找出这个数是多少?
本题包含多组数据:
每组数据包含两行。
第一行一个数字N(1<=N<=999999) ,保证N为奇数。
第二行为N个用空格隔开的整数。
数据以EOF结束。
对于每一组数据,你需要输出你找到的唯一的数。
Sample
Input:
5
1 3 2 3 3
11
1 1 1 1 1 5 5 5 5 5 5
7
1 1 1 1 1 1 1
Output:
3
5
1
Idea
题意:给定n个数,找出出现至少(n+1)/2次的数,这个数必然是唯一的
用map存储这n个数,key对应这个数,value对应这个数出现的次数
遍历map中的数,找到出现至少(n+1)/2次的那个数。
Summary
这题比较简单,关键是map的一些操作
Codes
#include <iostream>
#include <map>
using namespace std;
int n;
map<int,int> m;
int main()
{
while (scanf("%d",&n)!=EOF) {
map<int, int> m;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
if (m.find(a)==m.end())m.insert({ a,1 });
else m[a]++;
}
int pos=(n+1)/2;
for (auto i = m.begin(); i != m.end(); i++) {
if (i->second >= pos) { cout << i->first << endl; break; }
}
}
}
B - 必做题 - 2
Description
zjm被困在一个三维的空间中,现在要寻找最短路径逃生!
空间由立方体单位构成。
zjm每次向上下前后左右移动一个单位需要一分钟,且zjm不能对角线移动。
空间的四周封闭。zjm的目标是走到空间的出口。
是否存在逃出生天的可能性?如果存在,则需要多少时间?
输入第一行是一个数表示空间的数量。
每个空间的描述的第一行为L,R和C(皆不超过30)。
L表示空间的高度,R和C分别表示每层空间的行与列的大小。
随后L层,每层R行,每行C个字符。
每个字符表示空间的一个单元。’#‘表示不可通过单元,’.‘表示空白单元。
zjm的起始位置在’S’,出口为’E’。每层空间后都有一个空行。
L,R和C均为0时输入结束。
每个空间对应一行输出。
如果可以逃生,则输出如下
Escaped in x minute(s).
x为最短脱离时间。
如果无法逃生,则输出如下
Trapped!
Sample
Input:
3 4 5
S….
.###.
.##..
###.#
#####
#####
##.##
##…
#####
#####
#.###
####E
1 3 3
S##
#E#
###
0 0 0
Output:
Escaped in 11 minute(s).
Trapped!
Idea
题意:给定一个三维迷宫,要求从入口走到出口所需的最短时间。
- 用三维数组记录迷宫信息,并标记入口
- 自定义结构体pos,记录xyz坐标,以及当前移动的步数即时间
- BFS
将以起点为坐标,步数为0的pos压入队列,开始BFS搜索共6个方向,用vis记录一个点是否被遍历过,搜索时跳过超出三维空间或已经遍历到的点,将搜索到的点压入队列,重复操作。宽搜+避免重复遍历可以保证率先到达终点的那条路一定是最短的路。
Summary
这题是迷宫问题的变形,用BFS或DFS解题。由于题目要求出最短的时间即最短的一条路,所以用宽搜可以令各条路同步进行,率先完成的那条路就是最短的路。用深搜难以保证到达终点的那条路就是最短的路,而且容易爆栈。
Codes
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
char a[40][40][40];
bool vis[40][40][40] = { false };
int L, R, C;
int sx, sy, sz;
int dx[] = { 0,0,0,0,1,-1 };
int dy[] = { 0,0,-1,1,0,0 };
int dz[] = { 1,-1,0,0,0,0 };
struct pos {
int x, y, z, step;
pos(){}
pos(int a, int b, int c, int d) {
x = a, y = b, z = c, step = d;
}
};
void bfs()
{
queue<pos> q;
q.push(pos(sx,sy,sz,0));
vis[sx][sy][sz] = 1;
while (!q.empty())
{
pos p;
p = q.front();
q.pop();
if (a[p.x][p.y][p.z] == 'E')
{
printf("Escaped in %d minute(s).\n", p.step);
return;
}
for (int i = 0; i < 6; i++)
{
pos pp;
pp.x = p.x + dx[i];
pp.y = p.y + dy[i];
pp.z = p.z + dz[i];
pp.step = p.step;
if (a[pp.x][pp.y][pp.z] != '#'&&pp.x >= 0 && pp.x < L&&pp.y >= 0 && pp.y < R&&pp.z >= 0 && pp.z < C &&!vis[pp.x][pp.y][pp.z])
{
vis[pp.x][pp.y][pp.z] = 1;
pp.step++;
q.push(pp);
}
}
}
printf("Trapped!\n");
}
void init() {
for (int i = 0; i < L; i++)
for (int j = 0; j < R; j++)
for (int k = 0; k < C; k++)
vis[i][j][k] = false;
}
int main()
{
while (1) {
cin >> L >> R >> C;
if (L == 0 && R == 0 && C == 0)return 0;
init();
for (int i = 0; i < L; i++) {
for (int j = 0; j < R; j++)
{
cin >> a[i][j];
for (int k = 0; k < C; k++)
{
if (a[i][j][k] == 'S') { sx = i, sy = j, sz = k; }
}
}
}
//cout << sx << sy << sz << endl;
bfs();
}
}
C - 必做题 - 3
Description
东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)
输入m,输入n。后面跟着输入n个ai
输出最大和
数据量很大,需要scanf读入和dp处理。
Sample
Input:
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Output:
6
8
Idea
题意:有n个宿舍每个宿舍有数个人(可为负数),可以扫楼m次,每次扫楼可以扫一段连续的宿舍获得这些宿舍的人,求出m次扫楼可以获得的最大人数,数据量大用动态规划处理
- 定义状态f [i][j] 表示前i次扫楼扫到第j个宿舍可以获得的最大人数 i<=j<=n
- 状态转移方程f[i][j]=max(f[i][j-1]+a[j],maxk+a[j]),maxk=max(f[i-1][k]) 即前i-1次清扫可以获得的最大人数
- 由于n<=1e6,开不了这么大的二维数组,因此利用滚动数组优化
用pre代替原先的max(f[i-1][k]) 记录前i-1段中的最大人数,并在每层更新当前的最大人数
Summary
这题是动态规划的应用。状态转移方程有点难想,f[i][j]=max(f[i][j-1]+a[j],max(f[i-1][k])+a[j])
同时也要考虑大数据量,只能用滚动数组处理
Codes
#include <iostream>
#include <algorithm>
using namespace std;
int m, n;
int a[1000020],f[1000020],pre[1000020];
int main()
{
while (scanf("%d%d",&m,&n)!=EOF) {
memset(f, 0, sizeof(f));
memset(pre, 0, sizeof(pre));
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int maxk;
for (int i = 1; i <= m; i++) {
maxk = INT_MIN;
for (int j = i; j <= n; j++) {
f[j] = max(f[j - 1], pre[j - 1]) + a[j];
pre[j - 1] = maxk;
maxk = max(maxk, f[j]);
}
}
printf("%d\n", maxk);
}
}
D - 选做题 - 1
Description
We give the following inductive definition of a “regular brackets” sequence:
1.the empty sequence is a regular brackets sequence,
2.if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
3.if a and b are regular brackets sequences, then ab is a regular brackets sequence.
4.no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]
while the following character sequences are not:
(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.
Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].
The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.
For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.
Sample
Input:
((()))
()()()
([]])
)[)(
([][][)
end
Output:
6
6
4
0
6
Idea
题意:给定一个括号序列,找到最长的合法子序列的长度,子序列与子串不同之处在于可以不是连续的。
- 首先定义f[i][j]表示整个括号序列的第 i 位至第 j 位中最长合法子序列的长度
- 初始化f[i][j]=0,满足空序列为合法子序列
- 如果A、B分别是合法子序列,则AB为合法子序列
f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]) - 如果当前子序列的左右两端括号匹配,则最长合法子序列的长度加2
f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2) - 按照以上规则,从序列尾部开始向头部移动子序列的左边界,不断更新当前子序列中的最长合法子序列长度,最终答案为f[0][n-1]
Summary
这题是动态规划的应用,状态转移方程根据题目中的合法子序列的两个要求构造
分别有f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]),f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2)
每次改变左边界i时都要重新更新当前i至序列尾部中所有子串中最长的合法子序列长度
Codes
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
string str;
int f[110][110];
int main()
{
while (1) {
cin >> str;
if (str == "end")break;
int n = str.size();
memset(f, 0, sizeof(f));
for(int i=n-1;i>=0;i--)
for (int j = i + 1; j < n; j++) {
for (int k = i; k < j; k++)
f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
if ((str[i] == '('&&str[j] == ')') || (str[i] == '['&&str[j] == ']'))
f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2);
}
printf("%d\n", f[0][n - 1]);
}
}
E - 选做题 - 2
Description
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。
每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
Sample
Input:
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
Output:
2
Computer
Math
English
3
Computer
English
Math
Idea
题意:有n个作业,每个作业有完成时间与ddl,需在ddl前完成作业,超过ddl一天扣一分,要求出最少扣分且字典序最小的方案
-
自定义结构体task存储作业的名字、ddl、完成时间
-
要求输出字典序最小的方案,首先对这n个作业按字典序排序
-
定义状态S是完成作业的集合,f[S]表示完成S作业集合后被扣的最少分数
将S转换成二进制,即共有n位,每个作业对应一位,由于排序,字典序小的作业对应低位 -
状态转移方程:f[S|(1<<X)]=f[S]+max(sum+c[x]-d[x],0)
X表示当前欲完成的作业,如果X已经包含在S中则忽略
S|(1<<X)即把X添加到S集合中,sum表示完成S作业集合对应的总时间,c[x]表示X的完成时间,d[x]表示X的ddl,max(sum+c[x]-d[x],0)表示作业X被扣的分数 -
枚举
S从小到大枚举,X从小到大枚举,对于每个S集合实时计算完成它的时间sum
由于需要回溯输出方案,用pre记录每个状态S对应的最后一个作业
最后的f[S]就是最少扣分数,而最佳方案存储于数组逆向输出即可。
Summary
这题方法是动态规划+位运算,每个作业对应状态S中的一位,1表示完成了该作业,0表示未完成该作业,状态转移方程是f[S|(1<<X)]=f[S]+max(sum+c[x]-d[x],0)
需要注意的坑:
①枚举完所有S后,S处于10000…的状态,需要-1恢复到11111…,对应的f[S]才是正确答案
②对于每个状态S,要分别计算它的完成时间S
③由于作业序号从1开始,用到X进行位运算时需要写成1<<(X-1)
Codes
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int _,n;
struct task {
string name;
int ddl;
int days;
task(){}
task(string s, int d, int c) {
name = s, ddl = d, days = c;
}
bool operator<(task tt) {
return name < tt.name;
}
}t[20];
int f[1 << 15 + 1], pre[1 << 15 + 1];
int main()
{
cin >> _;
while (_--)
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t[i].name >> t[i].ddl >> t[i].days;
}
sort(t + 1, t + 1 + n);
int s = 0;
for (int i = 0; i <= (1<<n)-1; i++)pre[i] = 0,f[i]=1e8;
f[0] = 0;
for (s = 0; s <= (1 << n) - 1; s++) {
int sum = 0;
for (int i = 1; i <= n; i++) {
if (s&(1 << (i-1)))sum += t[i].days;
}
for (int i = 1; i <= n; i++) {
if (s&(1 << (i - 1)))continue;
int tmp = max(sum + t[i].days - t[i].ddl, 0);
if (f[s] + tmp < f[s | (1 << (i - 1))]) {
f[s | (1 << (i - 1))] = f[s] + tmp;
//int sm = sum[s];
pre[s | (1 << (i - 1))] = s;
}
}
}
s--;
printf("%d\n", f[s]);
vector<int> v;
while (s != 0)
{
int x = s - pre[s];
x = log2(x) + 1;
v.push_back(x);
s = pre[s];
}
for (int i = v.size() - 1; i >= 0; i--)
cout << t[v[i]].name << endl;
}
}