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[i−1][j]+a[i],另外,若本来只有j-1个段,则a[i]自成一新段,而我们不能确认在a[i]之前的是否属于哪个段,因此考虑所有可能情况即
d
p
[
k
]
[
j
−
1
]
+
a
[
i
]
dp[k][j-1]+a[i]
dp[k][j−1]+a[i],其中
j
−
1
<
=
k
<
=
i
−
1
j-1<=k<=i-1
j−1<=k<=i−1。
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][j−1]+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<=j−1,这里主要是检查有没有2个连续的子串可以相加,另外还有
d
p
[
i
]
[
j
−
1
]
dp[i][j-1]
dp[i][j−1],
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][len−1]就是答案。
总结:
区间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].c−work[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;
}