今天的专题是暴力枚举
蓝桥杯又名暴力杯,懂的都懂;主要是一些二进制枚举,在数据范围不大的时候可以用到,也可以骗分,当前不写二进制枚举,写暴搜更好(因为本人暴搜搜不明白)
T1:费解的开关
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
3
2
-1
解题思路:
我们可以发现,想要是全部灯打开,当第一行灯泡的状态的确定了,我们就可以通过变化第二行灯泡的状态来使第一行全亮,操作第3行又可以使第二行全亮。。。
一直递推下去,最后只要检查
1.步数是否合法
2.最后一行是否全亮(在此之间前4行一定为全亮)
所以我们枚举第一行的状态即可,2^5 = 32种状态
代码
// 思路:第一行确定了就确定了,枚举第一行的所有情况
// 然后向下递推
// 每一种情况验证:1.是否最后一行全被点亮
// 2.是否在6步之内
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
char a[6][6]; // 存图
char backup[6][6]; // 备份数组
int dx[] = {0,0,0,-1,1},dy[] = {0,-1,1,0,0};
void turn(int x,int y) { // 转换该点四周
a[x][y] ^= 1; // 先反转自己
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i],ny = y + dy[i];
if (nx < 0||nx > 4 ||ny < 0 ||ny > 4) continue;
a[nx][ny] ^= 1;
}
}
void solve() {
int min_steps = 1e9; // 最小步数
for (int i = 0;i < 5; i++)
for (int j = 0;j < 5; j++)
cin >> a[i][j];
for (int op = 0;op < 1<<5; op++) { // 枚举第一行的所有情况-2^5种
int steps = 0; // 记录步数
memcpy(backup,a,sizeof a); // 拷贝
for (int j = 0;j < 5; j++) {
if ((op >> j) & 1) { // 当前点要转换
steps++; // 记录步数
turn(0,j); // 转换
}
}
for (int i = 0;i < 4; i++) { // 处理中间三行
for (int j = 0;j < 5; j++) {
if (a[i][j] == '0') { // 对于一个灭的灯泡,通过它正下方的灯泡来点亮它
steps++;
turn(i + 1,j);
}
}
}
int f = 1;
// 判断1:最后一行是否全部点亮
for (int j = 0;j < 5 ;j++) {
if (a[4][j] == '0') {
f = 0;
break;
}
}
if (f) min_steps = min(min_steps,steps);
memcpy(a,backup,sizeof a); // 拷贝回来
}
if (min_steps > 6) cout << "-1" << '\n';
else cout << min_steps << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t; cin >> t;
while (t--) {
solve();
}
return 0;
}
T2:飞行员兄弟
-+--
----
----
-+--
6
1 1
1 3
1 4
4 1
4 3
4 4
解题思路:
该题和上一题类似,但是该题要记录操作,以及是枚举16个把手的状态,找最小操作次数
代码
// 这题和费解的开关有异曲同工之妙
// 费解的开关是枚举第一行的状态,该题则可直接枚举16个把手的状态
// O(6e4)的时间复杂度,不会超时
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
char a[5][5]; // 存原图
char backup[5][5];
void turn(int x,int y) {
for (int j = 1;j <= 4; j++) { // x行
a[x][j]=='+'?a[x][j]='-':a[x][j]='+';
}
for (int i = 1;i <= 4; i++) { // y列
a[i][y]=='+'?a[i][y]='-':a[i][y]='+';
}
a[x][y]=='+'?a[x][y]='-':a[x][y]='+';
}
void solve() {
vector<pair<int,int> > ans; // 记录操作
for (int i = 1;i <= 4; i++)
for (int j = 1;j <= 4; j++)
cin >> a[i][j];
for (int op = 0;op < 1<<16; op++) { // 枚举2^16种状态
vector<pair<int,int> > res;
memcpy(backup,a,sizeof a); // 拷贝
// 进行操作
for (int i = 0;i < 16; i++) {
if ((op >> i) & 1) {
int x = i / 4 + 1,y = i % 4 + 1;
res.push_back({x,y});
turn(x,y);
}
}
bool flag = true;
for (int i = 1;i <= 4; i++) {
for (int j = 1;j <= 4; j++) {
if (a[i][j] == '+') {
flag = false;
break;
}
}
}
// 开始时,ans.size()==0,一定要特判一下
if (flag && (ans.size() == 0 || res.size() < ans.size())) { // 寻找更优解
ans = res;
}
memcpy(a,backup,sizeof a); // 拷贝
}
cout << ans.size() << '\n';
for (auto x:ans) {
cout << x.f << " " << x.s << '\n';
}
return ;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T3:本题又主要考察了贪心(24牛客暑假训练营1)
3 4 3 2 4 5 8 1 2 1 4 2 4 3 1 3 1 1 2 3 6 6 1 2 3 4 5 6 2 3 2 3 3 4 4 5 5 6 6 1
1 1 4
解题思路
可以看到其中m<=10,最多有10场比赛,对于每一场比赛,要么是双方其中一者赢,要么是平局,我们枚举每一场比赛的状态,得到合法解的最优解,思路和上面两题相同,更难一些;
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
using namespace std;
const int N = 1e5 + 50;
int backup[150];
void solve() {
int ans = 100000;
int n,m; cin >> n >> m;
int a[n + 1]; // 存原始分
int m1 = 0;
vector<pair<int,int> >b(m + 1); // 存操作
vector<pair<int,int> >c(m + 1);
for (int i = 1;i <= n; i++) cin >> a[i];
for (int i = 0;i < m; i++) {
cin >> b[i].f >> b[i].s;
// 1.预处理有1的情况
if (b[i].f == 1 || b[i].s == 1) a[1]+=3;
// 没有1的数据存入c中,m1为数据个数
else c[++m1].f = b[i].f,c[m1].s = b[i].s;
}
for (int op1 = 0;op1 < 1<<m1; op1++) { // 0:加3 1: 加1
for (int op2 = 0;op2 < 1<<m1; op2++) { // 0:3分加给u,1:3分加给v
memcpy(backup,a,sizeof a); // 备份
for (int j = 0;j < m1; j++) {
// 2.都加1分
if (op1 & (1<<j)) { // 为1
a[c[j+1].f]++,a[c[j+1].s]++;
}
// 3.其中一人加3分
else {
if (op2 & (1<<j)) a[c[j+1].s] += 3;
else a[c[j+1].f] += 3;
}
}
// 取最靠前排名
int x = a[1];
sort(a + 1,a + n + 1,greater<int>());
for (int i = 1;i <= n; i++) {
if (a[i] == x) {
ans = min(ans,i);
break;
}
}
memcpy(a,backup,sizeof a); // 复制回来
}
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t; cin >> t;
while (t--) {
solve();
}
return 0;
}
其实更简单的是写暴搜,可自行尝试(doge)
T4:心绪的解剖(24牛客寒假训练营6)
5
5
10
15
20
25
0 0 5
2 0 8
5 5 5
2 5 13
21 1 3
解题思路
预处理 + 枚举 + 剪枝
初看是一个很难得题:范围1e9这怎么枚举啊?
但是注意斐波那契的增长数组是非常快的,1e9大概是它的40项左右,所以我们完全可以直接枚举3个数a,b,c,如果和为斐波那契,将三个数和数的和存入一个map中,预处理完直接离线查询即可;
o(40*40*40)的时间复杂复,稍微要加一些剪枝
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
struct node {
int x,y,z;
};
int fibo[100];
map<int,node>mp;
int cnt;
void init() { // 初始化斐波那契
fibo[0] = 0;
fibo[1] = 1,fibo[2] = 1;
for (int i = 3;; i++) {
fibo[i] = fibo[i - 1] + fibo[i - 2];
if (fibo[i] > 1e9) {
cnt = i;
break;
}
}
}
void solve() {
init();
for (int i = 0;i <= cnt; i++) { // 预处理
for (int j = 0;j <= cnt; j++) {
if (fibo[i] + fibo[j] > 1e9) break;
for (int k = 0;k <= cnt; k++) {
int sum = fibo[i] + fibo[j] + fibo[k];
if (sum > 1e9) break;
mp[sum] = {fibo[i],fibo[j],fibo[k]};
}
}
}
int q; cin >> q;
// 离线查询
while (q--) {
int n; cin >> n;
if (!mp.count(n)) {
cout << "-1" << '\n';
}
else cout << mp[n].x << " " << mp[n].y << " " << mp[n].z << '\n';
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T5:带分数(第四届蓝桥杯)
输入1
100
输出1
11
输入2
105
输出2
6
解题思路
由于式子的右边一定由1~9的排列组成,所以我们可以直接计算出1~9的所有排列情况,然后枚举两个断点,使得整个排列分为三段,分别为a,b,c,在判断是否为合法的答案,记录合法答案个数即可
注意:
- x = a + b / c,可能出现无法整除但是int自动向下取整的情况,所以不能用除法算式来判断;两边同乘c => x * c = a * c + b
- 求全排列可以用dfs和next_permutation全排列函数
代码
dfs
// x = a + b / c,我们尝试1-9的所有全排列,然后枚举两个点将其分为三段
// n * c = b + a * c
// 带入表达式是否成立
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 50;
int x; // 目标数
bool st[10];
int a[10]; // 记录全排列数组
int ans = 0; // 记录答案
int cal(int l,int r) {
int res = 0;
for (int i = l;i <= r; i++) {
res = res * 10 + a[i];
}
return res;
}
void dfs(int cnt) {
if (cnt == 10) {
// 枚举两个断点 [1,l][l+1,r][r+1,9]
for (int l = 1;l <= 7; l++) {
for (int r = l + 1;r <= 8; r++) {
int a = cal(1,l);
int b = cal(l + 1,r);
int c = cal(r + 1,9);
if (x*c == a*c + b) ans++;
}
}
return ;
}
for (int i = 1;i <= 9; i++) {
if (st[i]) continue;
a[cnt] = i;
st[i] = true;
dfs(cnt + 1);
st[i] = false; // 回溯
}
return ;
}
void solve() {
cin >> x;
dfs(1);
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
函数
// x = a + b / c,我们尝试1-9的所有全排列,然后枚举两个点将其分为三段
// n * c = b + a * c
// 带入表达式是否成立
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 50;
int x; // 目标数
bool st[10];
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int ans = 0; // 记录答案
int cal(int l,int r) {
int res = 0;
for (int i = l;i <= r; i++) {
res = res * 10 + a[i];
}
return res;
}
void solve() {
cin >> x;
do {
for (int l = 1;l <= 7; l++) {
for (int r = l + 1;r <= 8; r++) {
int a = cal(1,l);
int b = cal(l + 1,r);
int c = cal(r + 1,9);
if (x * c == a * c + b) ans++;
}
}
}while(next_permutation(a + 1,a + 10));
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}