A.迷宫
题目
X星球的一处迷宫游乐场建在某个小山坡上。它是由10x10相互连通的小房间组成的。 房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立,则: L表示走到左边的房间,R表示走到右边的房间,U表示走到上坡方向的房间,D表示走到下坡方向的房间。 X星球的居民有点懒,不愿意费力思考。他们更喜欢玩运气类的游戏。这个游戏也是如此! 开始的时候,直升机把100名玩家放入一个个小房间内。玩家一定要按照地上的字母移动。 迷宫地图如下:
UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR
请你计算一下,最后,有多少玩家会走出迷宫? 而不是在里边兜圈子。
思路
只需要一个接一个遍历,通过dfs,简单搜索即可得到答案
代码
#include <bits/stdc++.h>
using namespace std;
#define mm(a) memset(a,0,sizeof(a));
int Map[10][10]; // 记录该位置是否被访问
string data[10]; // 存放数据
bool dfs(int row, int col) {
if (row < 0 || row > 9 || col < 0 || col > 9) return true;
if (Map[row][col] == 1) return false;
Map[row][col] = 1;
if (data[row][col] == 'U') return dfs(row - 1, col);
else if(data[row][col] == 'D') return dfs(row + 1, col);
else if (data[row][col] == 'L') return dfs(row, col - 1);
else return dfs(row, col + 1);
}
int main()
{
// 读入数据
data[0] = "UDDLUULRUL";
data[1] = "UURLLLRRRU";
data[2] = "RRUURLDLRD";
data[3] = "RUDDDDUUUU";
data[4] = "URUDLLRRUU";
data[5] = "DURLRLDLRL";
data[6] = "ULLURLLRDU";
data[7] = "RDLULLRDDD";
data[8] = "UUDDUDUDLL";
data[9] = "ULRDLUURRR";
int res = 0;
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 10; col++) {
mm(Map);
if (dfs(row, col)) res++;
}
}
cout << res << endl;
return 0;
}
B.跳蚱蜢
题目
如图所示: 有9只盘子,排成1个圆圈。其中8只盘子内装着8只蚱蜢,有一个是空盘。
我们把这些蚱蜢顺时针编号为 1~8。每只蚱蜢都可以跳到相邻的空盘中,也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。 请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,并且保持空盘的位置不变(也就是1-8换位,2-7换位,...),至少要经过多少次跳跃?
思路
直接让蚱蜢跳到空盘有点麻烦。如果看成空盘跳到蚱蜢的位置就简单多了,只有一个空盘在跳。
题目给的是一个圆圈,不好处理,此时祭出一个建模大法:“化圆为线”! 把空盘看成0,
那么有9个数字{0,1,2,3,4,5,6,7,8}
,一个圆圈上的9个数字,拉直成了一条线上的9个数字。
八数码是经典的BFS问题。
八数码有9个数字{0,1,2,3,4,5,6,7,8}
,它有9!=362880
种排列。也不多,
本题的初始状态是“012345678”,终止状态是“087654321
”。
把9个数字的排列定义为一种状态,即字符串s,例如初始状态“012345678
”是一个串。对
应交换之后产生的s,我们可以使用map判重,将该字符串以及它首次出现的时间作为一个单
位推入队列中,由于BFS的性质我们能看出,首次找到结果状态的时间t即是最小的答案。
代码
#include <bits/stdc++.h>
using namespace std;
#define mm(a) memset(a,0,sizeof(a));
struct node{
node() {}
node(string ss, int tt) {
s = ss;
step = tt;
}
string s;
int step;
};
map<string, bool> mp; // 表示 某一个字符串是否被遍历
queue<node> q; // 队列用与bfs
void bfs() {
while (!q.empty()) {
node cur = q.front();
q.pop();
string s = cur.s;
int step = cur.step;
if (s == "087654321") {
cout << step << endl;
break;
}
// 找到空位
int pos = 0;
while (s[pos] != '0') pos++;
// 交换位置 重新入队
for (int sp = pos - 2; sp <= pos + 2; sp++) {
int k = (sp + 9) % 9; // 为了形成环形 需要 加9之后模9
if (k == pos) continue;
char temp;
temp = s[pos]; s[pos] = s[k]; s[k] = temp; // 交换得到新的字符串
if (!mp[s]) {
mp[s] = true;
q.push(node(s, step + 1));
}
temp = s[pos]; s[pos] = s[k]; s[k] = temp; // 重新交换回来,进行判断下一个
}
}
}
int main() {
string s = "012345678";
q.push(node(s, 0));
mp[s] = true;
bfs();
return 0;
}
C.模仿状态
题目
二阶魔方就是只有2层的魔方,只由8个小块组成。如图所示。 小明很淘气,他只喜欢3种颜色,所有把家里的二阶魔方重新涂了颜色,如下: 前面:橙色、右面:绿色、上面:黄色、左面:绿色、下面:橙色、后面:黄色 请你计算一下,这样的魔方被打乱后,一共有多少种不同的状态。 如果两个状态经过魔方的整体旋转后,各个面的颜色都一致,则认为是同一状态。
思路
代码
#include <bits/stdc++.h>
using namespace std;
#define mm(a) memset(a,0,sizeof(a));
int main() {
cout << "229878" << endl;
return 0;
}
D.方格分割
题目
6x6的方格,沿着格子的边线剪开成两部分。要求这两部分的形状完全相同。如图就是可行的分割法。 试计算:包括这3种分法在内,一共有多少种不同的分割方法。注意:旋转对称的属于同一种分割法。
思路
回溯算法(DFS):由题意,这一条切割线必定经过图的中心点,那么我们一旦确定了半条到达边界的分割线,就能根据这半条对称画出另外半条。而由于结果中心对称性,搜索出来的个数应该除以4得出最终结论。
- 中心点是(3,3),从(3,3)出发,向右、向左、向上、向下,四个方向DFS即可。
可以直接套用回溯的模版就可以了
代码
#include <bits/stdc++.h>
using namespace std;
#define mm(a) memset(a,0,sizeof(a));
int Map[10][10];
int res = 0;
int X[] = {0, -1, 1, 0, 0};
int Y[] = {0, 0, 0, -1, 1};
void dfs(int x, int y) {
if (x == 0 || y == 0 || x == 6 || y == 6) {
res++;
return ;
}
for (int i = 1; i <= 4; i++) {
x += X[i], y += Y[i];
if (!Map[x][y]) {
Map[x][y] = 1;
Map[6 - x][6 - y] = 1;
dfs(x, y);
Map[6 - x][6 - y] = 0;
Map[x][y] = 0;
}
x -= X[i], y -= Y[i];
}
}
int main() {
mm(Map);
Map[3][3] = 1;
dfs(3, 3);
cout << res / 4 << endl;
return 0;
}
E.字母组串
题目
由 A,B,C 这3个字母就可以组成许多串。 比如:"A","AB","ABC","ABA","AACBB" ....
现在,小明正在思考一个问题: 如果每个字母的个数有限定,能组成多少个已知长度的串呢?
他请好朋友来帮忙,很快得到了代码, 解决方案超级简单,然而最重要的部分却语焉不详。
请仔细分析源码,填写划线部分缺少的内容。
#include <stdio.h>
// a个A,b个B,c个C 字母,能组成多少个不同的长度为n的串。
int f(int a, int b, int c, int n)
{
if(a<0 || b<0 || c<0) return 0;
if(n==0) return 1;
return ______________________________________ ; // 填空
}
int main()
{
printf("%d\n", f(1,1,1,2));
printf("%d\n", f(1,2,3,3));
return 0;
}
思路
动态规划(DP)
很明显,有一个状态关系式,f(n) = f(n - 1) {A, B, C}
第n个字符串就是第n - 1
个字符串后面加A或B或C,有点类似于爬楼梯那个递归方程。
代码
return f(a - 1, b, c, n - 1) + f(a, b - 1, c, n - 1) + f(a, b, c - 1, n - 1);
F.最大公共子串
题目
最大公共子串长度问题就是: 求两个串的所有子串中能够匹配上的最大长度是多少。
比如:“abcdkkk” 和 “baabcdadabc”, 可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。
下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。
#include <stdio.h>
#include <string.h>
#define N 256
int f(const char* s1, const char* s2)
{
int a[N][N];
int len1 = strlen(s1);
int len2 = strlen(s2);
int i,j;
memset(a,0,sizeof(int)*N*N);
int max = 0;
for(i=1; i<=len1; i++){
for(j=1; j<=len2; j++){
if(s1[i-1]==s2[j-1]) {
a[i][j] = __________________________; //填空
if(a[i][j] > max) max = a[i][j];
}
}
}
return max;
}
int main()
{
printf("%d\n", f("abcdkkk", "baabcdadabc"));
return 0;
}
思路
这道题也是经典的动态规划题目,只需要找到状态转移方程式,就可以得到答案。
代码
a[i][j] = a[i - 1][j - 1] + 1;
G.正则问题
题目
考虑一种简单的正则表达式:只由 x ( ) | 组成的正则表达式。 小明想求出这个正则表达式能接受的最长字符串的长度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6
输入
输入一个由x()|组成的正则表达式。输入长度不超过100,保证合法。
如:
((xx|xxx)x|(x|xx))xx
输出
输出这个正则表达式能接受的最长字符串的长度。
6
思路
之前做过类似的题目,读题没仔细,看到题目就直接做了,然后提交,38分,然后改代码,提交63分,再改代码,75分。
然后就放弃了,去搜了一下别人的思路,看了下也是用栈,但是看了下别人的分析,我发现我忽略了一点很关键的东西:
以这个为例子:(()),我直接默认为了(())内层两个匹配,但是可能(()**)**这样匹配。
代码
如果按照我的思路那样匹配的话,直接用栈,就可以完成正则匹配
#include <bits/stdc++.h>
using namespace std;
#define mm(a) memset(a,0,sizeof(a));
#define max(x,y) (x)>(y)?(x):(y)
#define min(x,y) (x)<(y)?(x):(y)
int main() {
stack<char> stk;
string s;
cin >> s;
for (int i = 0; i < s.length(); i++) {
if (s[i] == ')') {
string cur = ""; // 定义一个字符串去 接收栈里的表达式
while (stk.top() != '(') {
cur += stk.top();
stk.pop();
}
stk.pop(); // pop 出‘(’
int j;
int MAX = 0, cnt = 0;
for (j = 0; j < cur.length(); j++) {
if (cur[j] == '|') {
MAX = max(MAX, cnt);
cnt = 0;
} else {
cnt++;
}
}
MAX = max(MAX, cnt);
while (MAX--) stk.push('x');
} else {
stk.push(s[i]);
}
}
string cur = "";
while (!stk.empty()) {
cur += stk.top();
stk.pop();
}
int j;
int MAX = 0, cnt = 0;
for (j = 0; j < cur.length(); j++) {
if (cur[j] == '|') {
MAX = max(MAX, cnt);
cnt = 0;
} else {
cnt++;
}
}
MAX = max(MAX, cnt);
cout << MAX << endl;
return 0;
}
改进之后
#include <bits/stdc++.h>
using namespace std;
string s = ""; // target string
int pos = 0; // current postion
int dfs() {
int tmp = 0; // temporary variable to store the character of x
int ret = 0; // return result;
int len = s.size();
while (pos < len) {
if (s[pos] == '(') {
// ( continue recursion, equivalent to stacking
pos++;
tmp += dfs();
} else if (s[pos] == ')') {
// ) return recursion, equivalent to popping
pos++;
break;
} else if (s[pos] == '|') {
// check or
pos++;
ret = max(ret, tmp);
tmp = 0;
} else {
// calculate and count the character of x
pos++;
tmp++;
}
}
return max(ans, tmp);
}
int main() {
cin >> s;
cout << dfs() << endl;
return 0;
}
H.包子凑数
题目
题目描述
小明几乎每天早晨都会在一家包子铺吃早餐。这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子 每种蒸笼都有非常多笼,可以认为是无限笼。 每当有顾客想买X个包子,卖包子的大叔就会选出若干笼包子来,使得这若干笼中恰好一共有X个包子。 比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。 当然有时包子大叔无论如何也凑不出顾客想买的数量。 比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。 小明想知道一共有多少种数目是包子大叔凑不出来的。
输入
第一行包含一个整数N。(1 <= N <= 100) 以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出
输出一行包含一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
样例输入
2
4
5
样例输出
6
提示
对于样例,凑不出的数目包括:1, 2, 3, 6, 7, 11。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 13000;
int a[101];
int dp[MAXN] = {0};
int gcd(int a, int b) {
return b > 0 ? gcd(b, a % b) : a;
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int g = a[1];
for (int i = 2; i <= n; i++)
g = gcd(g, a[i]);
if (g != 1) {
cout << "INF" << endl;
return 0;
}
for (int i = 1; i <= n; i++) {
dp[a[i]] = 1; // 遍历第i个数据 表是那个值是计算过的
for (int j = 0; j + a[i] < 10000; j++) {
if (dp[j]) {
dp[j + a[i]] = 1; // 原本那个可以筹出来的数 + 当前这个数字 也一定是一个能够筹出来的数字
}
}
}
int res = 0;
for (int i = 1; i < 10000; i++) {
if (dp[i] == 0) res++;
}
cout << res << endl;
return 0;
}
I.分巧克力
题目
题目描述
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。 小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。 为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
- 例如一块
6x5
的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。 - 当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
- 例如一块
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000
)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000
)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出
输出切出的正方形巧克力最大可能的边长。
样例输入
2 10
6 5
5 6
样例输出
2
思路
暴力枚举: 但是数据规模是10万,n个长方形,长方形的最大边长D,复杂度是O ( n × D ) ,10^10会TLE
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
int h[MAXN], w[MAXN];
int n, k;
bool check(int d) {
int num = 0;
for (int i = 0; i < n; i++)
num += (h[i] / d) * (w[i] / d);
if (num >= k) return true;
else return false;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++)
cin >> h[i] >> w[i];
int d = 1;
while (1) {
if (check(d))
d++;
else
break;
}
cout << d - 1 << endl;
return 0;
}
二分优化:既然找最大的那条边D太高,有二分查找可以降为logD
代码
OJ上AC c++代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
int h[MAXN], w[MAXN];
int n, k;
bool check(int d) {
int num = 0;
for (int i = 0; i < n; i++)
num += (h[i] / d) * (w[i] / d);
if (num >= k) return true;
else return false;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++)
cin >> h[i] >> w[i];
int l = 1, r = MAXN;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid))
l = mid + 1;
else
r = mid - 1;
}
cout << l - 1 << endl;
return 0;
}
J.付账问题
题目
题目描述
几个人一起出去吃饭是常有的事。但在结帐的时候,常常会出现一些争执。 现在有 n 个人出去吃饭,他们总共消费了 S 元。其中第 i 个人带了 ai 元。 幸运的是,所有人带的钱的总数是足够付账的。但现在问题来了:每个人分别要出多少钱呢? 为了公平起见,我们希望在总付钱量恰好为 S 的前提下,最后每个人付的钱的标准差最小。 这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是1分钱的整数倍。你需要输出最小的标准差是多少。 标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。 形式化地说,设第 i 个人付的钱为 bi 元,那么标准差为 :
输入
第一行包含两个整数 n、S; 第二行包含 n 个非负整数 a1, ..., an。 n ≤ 5 × 10^5, 0 ≤ ai ≤ 10^9。
输出
输出最小的标准差,四舍五入保留 4 位小数。 保证正确答案在加上或减去 10^−9 后不会导致四舍五入的结果发生变化。
样例输入
10 30
2 1 4 7 4 8 3 6 4 7
样例输出
0.7928
思路
- 对a i 从小到大排序;
- 前一部分人的钱不够,那么就出他们所有的钱;
- 从总付钱数中扣除前一部分人出的钱,得剩余钱数为S ’,以及后一部分人的出钱平均数avg。
- 后一部分人的钱多,他们多出一些。怎么出?这部分人也分两类:
- 比较有钱的,但是他的钱也不够avg ,那么他的钱还是要全出;
- 非常有钱的,不管怎么摊他都有富余。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e5;
typedef long long ll;
ll a[MAXN];
int main() {
int n;
ll s;
cin >> n >> s;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
double avg = 1.0 * s / n;
double sum = 0;
for (int i = 1; i <= n; i++) {
if (a[i] * (n + 1 - i) < s) { // 需要把钱全拿出的人:(1)钱不够平均数的,(2)钱够平均数,但也不是很多的
sum += (a[i] - avg) * (a[i] - avg);
s -= a[i]; // 更新剩余钱
} else {
// 不用把钱全拿出的人:非常有钱,不管怎么平均都够
double cur_avg = 1.0 * s / (n + 1 - i); // 更新平均出钱
sum += pow((cur_avg - avg), 2) * (n + 1 - i);
break;
}
}
printf("%.4f\n", sqrt(sum / n));
return 0;
}