【Week 12 作业】A - 必做题 - 1、B - 必做题 - 2、C - 必做题 - 3、D - 选做题 - 1、E - 选做题 - 2

A - 必做题 - 1

题意:

给出n个数,zjm想找出出现至少(n+1)/2次的数, 现在需要你帮忙找出这个数是多少?

Input

本题包含多组数据:
每组数据包含两行。
第一行一个数字N(1<=N<=999999) ,保证N为奇数。
第二行为N个用空格隔开的整数。
数据以EOF结束。

Output

对于每一组数据,你需要输出你找到的唯一的数。

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

Sample Output

3
5
1

思路做法:

输入的同时用map记录出现多少次,若超过阈值,记录答案

总结:

map要初始化

代码:

#include <stdio.h>
#include <map>
using namespace std;
map<int, int> m;
// 1e7 O(n)
int main(){
	int n; 
	while(~scanf("%d", &n)){
		m.clear();
		int ans, b = (n + 1) / 2;
		for(int i = 0; i < n; ++i){
			int a; scanf("%d", &a);
			if(++m[a] >= b) ans = a;
		}
		printf("%d\n", ans);
	}
	return 0;
}

B - 必做题 - 2

题意:

zjm被困在一个三维的空间中,现在要寻找最短路径逃生!
空间由立方体单位构成。
zjm每次向上下前后左右移动一个单位需要一分钟,且zjm不能对角线移动。
空间的四周封闭。zjm的目标是走到空间的出口。
是否存在逃出生天的可能性?如果存在,则需要多少时间?

Input

输入第一行是一个数表示空间的数量。
每个空间的描述的第一行为L,R和C(皆不超过30)。
L表示空间的高度,R和C分别表示每层空间的行与列的大小。
随后L层,每层R行,每行C个字符。
每个字符表示空间的一个单元。’#‘表示不可通过单元,’.‘表示空白单元。
zjm的起始位置在’S’,出口为’E’。每层空间后都有一个空行。
L,R和C均为0时输入结束。

Output

每个空间对应一行输出。
如果可以逃生,则输出如下
Escaped in x minute(s).
x为最短脱离时间。

如果无法逃生,则输出如下
Trapped!

Sample Input

3 4 5
S....
.###.
.##..
###.#

#####
#####
##.##
##...

#####
#####
#.###
####E

1 3 3
S##
#E#
###

0 0 0

Sample Output

Escaped in 11 minute(s).
Trapped!

思路做法:

因为要找最短路径,因此用BFS

总结:

这是一个三维的BFS,但是除了维度外并没有什么不同

代码:

#include <stdio.h>
#include <queue>
using namespace std;

const int N = 35;
char m[N][N][N];
bool isF[N][N][N];  // 是否走过 
struct Unit{
	int l, r, c, t;
	Unit(){}
	Unit(int _l, int _r, int _c, int _t):l(_l), r(_r), c(_c), t(_t){}
	bool operator < (const Unit& u) const {
		if(l != u.l) return l < u.l;
		if(r != u.r) return r < u.r;
		return c < u.c;
	}
}s, e;
queue<Unit> q;

const int dx[] = {-1, 1, 0, 0, 0, 0};
const int dy[] = {0, 0, -1, 1, 0, 0};
const int dz[] = {0, 0, 0, 0, -1, 1};
int l, r, c;
// BFS 
int findPath(Unit s){
	q.push(s); isF[s.l][s.r][s.c] = true;
	while(!q.empty()){
		Unit cur = q.front(); q.pop();
		for(int i = 0; i < 6; ++i){
			int x = cur.l+dx[i], y = cur.r+dy[i], z = cur.c+dz[i];
			Unit tmp(x, y, z, cur.t + 1);
			if(x >= 0 && x < l && y >= 0 && y < r && z >= 0 && z < c && !isF[x][y][z]){
				if(m[x][y][z] == 'E') return cur.t + 1;
				if(m[x][y][z] == '.'){
					q.push(tmp);
				}
				isF[x][y][z] = true;
			}
		}
	}
	
	return -1;  // 没有路径 
}

int main(){
	while(scanf("%d%d%d", &l, &r, &c)){
		if(l == 0 && r == 0 && c == 0) break;
		while(!q.empty()) q.pop();  // 初始化
		getchar();
		for(int i = 0; i < l; ++i){
			for(int j = 0; j < r; ++j){
				for(int k = 0; k < c; ++k){
					scanf("%c", &m[i][j][k]);
					isF[i][j][k] = false;
					if(m[i][j][k] == 'S') s = Unit(i, j, k, 0);
				}
				getchar();
			}
			getchar();
		}
		
		int ans = findPath(s);
		if(ans == -1) printf("Trapped!\n");
		else printf("Escaped in %d minute(s).\n", ans);
	}
	return 0;
}

C - 必做题 - 3

题意:

东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有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)

Input

输入m,输入n。后面跟着输入n个ai 处理到 EOF

Output

输出最大和

Sample Input

1 3 1 2 3
2 6 -1 4 -2 3 -2 3

Sample Output

6
8 

Hint

数据量很大,需要scanf读入和dp处理。

思路做法:

状态定义:题目中涉及2个量:宿舍序号及扫了几次楼,也就是i个宿舍及不相交的j个段,其中第i个宿舍属于第j个段, d p [ i ] [ j ] dp[i][j] dp[i][j]表示这种情况下的人数最大和。
状态转移:考虑 d p [ i ] [ j ] dp[i][j] dp[i][j]可以怎么得来,首先若本来就有j个段,则a[i-1]必定属于第j个段,再之前的不能确认,而a[i]也要属于第j段,因此可以是 d p [ i − 1 ] [ j ] + a [ i ] dp[i-1][j]+a[i] dp[i1][j]+a[i],另外,若本来只有j-1个段,则a[i]自成一新段,而我们不能确认在a[i]之前的是否属于哪个段,因此考虑所有可能情况即 d p [ k ] [ j − 1 ] + a [ i ] dp[k][j-1]+a[i] dp[k][j1]+a[i],其中 j − 1 < = k < = i − 1 j-1<=k<=i-1 j1<=k<=i1 d p [ i ] [ j ] dp[i][j] dp[i][j]取这些状态的最大值。
这样的代码可以跑出答案来,因为思路是对的,但是还要优化。
滚动数组:能否减少一个维度。只有i所属维度在每次更新时用的全是前面的数据,尝试把j所属维度降维。它考虑的是j-1个段时的最大值,因此用一个数组Max记录并更新这个值,当段从少到多时,Max记录的是j-1个段时的当前i数量下的最大值,并且它的更新始终慢一步,代码中用Max[i-1],因为a[i]不能算进去。答案是最后一轮更新完的temp值。

总结:

j从1到m循环时,i可以直接从j开始循环。
个人觉得这道做优化最难,状态与转移要想,然后还要转变为滚动数组,不然过不去。
另外,数据最大会超int,但是用int的ans过了。

代码:

#include <stdio.h>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1e6+50;
const int inf = 1e9;
int a[N];
int dp[N], Max[N];
/*
状态:dp[i][j] 前i个数 选择了j个区间 且a[i]属于区间j 的最大和 
转移:dp[i][j] = max(dp[i-1][j]+a[i], dp[k][j-1]+a[i]), j-1<=k<=i-1
滚动数组:dp[i] = max(dp[i-1]+a[i], Max[i-1]+a[i])
Max[]  dp[k][j-1] j-1<=k<=i-1
*/
int main(){
	int m, n; 
	while(~scanf("%d%d", &m, &n)){
		for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
		dp[0] = 0; int temp;
		for(int i = 0; i <= n; ++i) Max[i] = 0;  // 初始为0 
		for(int j = 1; j <= m; ++j){
			temp = -inf;
			for(int i = j; i <= n; ++i){
				dp[i] = max(dp[i-1]+a[i], Max[i-1]+a[i]);
				Max[i-1] = temp;  // 更新为上一个最大和 
				temp = max(temp, dp[i]);
//				printf("%d %d temp = %d\n", j, i, temp);
			}
		}
		printf("%d\n", temp);
	}
	return 0;
}

D - 选做题 - 1

题意:

We give the following inductive definition of a “regular brackets” sequence:

the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
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 [([])].

Input

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.

Output

For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

Sample Input

((()))
()()()
([]])
)[)(
([][][)
end

Sample Output

6
6
4
0
6

思路做法:

可以用区间DP求解。定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j]为区间 [ i , j ] [i, j] [i,j]内的longest possible regular brackets subsequence的长度。
d p [ i ] [ j ] dp[i][j] dp[i][j]可以由谁转化过来呢?根据题目给出的条件可知:若 s [ i ] s[i] s[i] s [ j ] s[j] s[j]是配对的括号,则 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由 d p [ i + 1 ] [ j − 1 ] + 2 dp[i+1][j-1]+2 dp[i+1][j1]+2转化过来, d p [ i ] [ k ] + d p [ k + 1 ] [ j ] , i < = k < = j − 1 dp[i][k] + dp[k+1][j], i<=k<=j-1 dp[i][k]+dp[k+1][j],i<=k<=j1,这里主要是检查有没有2个连续的子串可以相加,另外还有 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j],在这些值里取最大值,就是当前 d p [ i ] [ j ] dp[i][j] dp[i][j]所能达到的最大长度。最后取 d p [ 0 ] [ l e n − 1 ] dp[0][len-1] dp[0][len1]就是答案。

总结:

区间DP的循环先按区间从小到大,再按前后顺序依次更新,这道题的状态和转移还是不难的。

代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 150;
char s[N];
int dp[N][N];
/*
dp[i, j]  [i, j] 区间内的最长子序列长度
dp[i, j] = dp[i+1, j-1]+2 if s[i]?s[j]  ()[]
		   dp[i, j-1]
		   dp[i+1, j]
		   dp[i, k] + dp[k+1, j]  i<=k<=j-1
*/
int main(){
	while(scanf("%s", s)){
		if(!strcmp(s, "end")) break;
		int len = strlen(s);
		for(int i = 0; i < len; ++i) dp[i][i] = 0;
//		for(int i = 0; i < len-1; ++i) dp[i][i+1] = 0;
		for(int sz = 2; sz <= len; ++sz){
			for(int i = 0; i < len-sz+1; ++i){
				int l = i, r = i+sz-1; dp[l][r] = 0;

				if(s[l]=='('&&s[r]==')'||s[l]=='['&&s[r]==']'){
					if(l+1<=r-1) dp[l][r] = max(dp[l][r], dp[l+1][r-1] + 2);
					else dp[l][r] = max(dp[l][r], 2);
				}
				dp[l][r] = max(dp[l][r], max(dp[l][r-1], dp[l+1][r]));
				for(int k = l; k <= r-1; ++k){
					dp[l][r] = max(dp[l][r], dp[l][k]+dp[k+1][r]);
				}
//				printf("%d %d %d\n", l, r, dp[l][r]);		
			}
		}
		printf("%d\n", dp[0][len-1]);
	}
	return 0;
}

E - 选做题 - 2

题意:

马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。

Input

有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。

Output

每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。

Sample Input

2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3

Sample Output

2
Computer
Math
English
3
Computer
English
Math

Hint

在第二个样例中,按照 Computer->English->Math 和 Computer->Math->English 的顺序完成作业,所扣的分数都是 3,由于 English 的字典序比 Math 小,故输出前一种方案。

思路做法:

状压DP求解。作业最多15个,因此可以用一个数来表示当前完成作业的情况,即最多15位的二进制数(代码中用33000代替)可以表示所有可能状态。这是DP的状态S,还要考虑如何转移。考虑从小到大递增过来的一个状态S,遍历所有作业,看看是不是还没完成,若是,则计算时间并尝试更新dp数组的值(初始为一个inf),使之达到更小。其中超时时间 t = m a x ( s u m [ s ] + w o r k [ i ] . c − w o r k [ i ] . d , 0 ) t = max(sum[s]+work[i].c-work[i].d, 0) t=max(sum[s]+work[i].cwork[i].d,0),即花费时间不能小于0,sum是已用时间,用上一个 s u m [ s ] + w o r k [ i ] . c sum[s] + work[i].c sum[s]+work[i].c更新。每一次更新需要记录路径即作业序号,以便之后回溯输出。

总结:

状压DP先要把状态压缩,一般数不会很大,之后主要考虑如何转移。该题的转移是看当前状态下通过完成1门作业还能往哪个更大一点的状态转移,并尝试更新花费的最小值。输出时由于要按字典序因此可以先按名称排序,结果一定是按序的。

代码:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int N = 33000;
const int inf = 1e9;
// 状压DP
int dp[N], sum[N], pre[N];  // 2^15  所有状态 
// dp[s]:完成s作业集合后被扣除的最少分数 
struct Work{
	string s;
	int d, c;
	Work(){}
	Work(string _s, int _d, int _c):s(_s), d(_d), c(_c){}
	bool operator < (const Work& w) const {
		return s < w.s;
	}
}work[16];
void output(int s){
	if(s == 0) return;
	output(s^(1<<pre[s]));
	cout << work[pre[s]].s << endl;
} 
int main(){
	int cnt; cin >> cnt;
	while(cnt--){  // cnt组数据 
		int n; cin >> n;  // n个任务 
		for(int i = 0; i < n; ++i){
			cin >> work[i].s >> work[i].d >> work[i].c;
		}
		sort(work, work+n);  // 按名称字典序排序
		int m = (1<<n)-1;
		for(int i = 0; i <= m; ++i){  // 初始化 
			dp[i] = inf; sum[i] = 0; pre[i] = -1;
		}
		dp[0] = 0;
		for(int s = 0; s <= m; ++s){
			for(int i = 0; i < n; ++i){
				int j = (1<<i);
				if(!(s&j)){  // s对应位置为0 
					int t = max(sum[s]+work[i].c-work[i].d, 0);  // 转移后所需总时间 
					if(dp[s|j] > dp[s] + t){  // 可以更优 
						dp[s|j] = dp[s] + t;
						pre[s|j] = i;  // 记录路径
						sum[s|j] = sum[s] + work[i].c;  // 更新总时间 
					}
				}
			}
		}
		cout << dp[m] << endl;
		output(m);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容嬷嬷当年一枝花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值