会独立敲一遍代码并debug(1,3题较难;2,4题较简单)
部分题需要买课,可到洛谷或其他OJ找原题
目录
空间复杂度(计算方法)
1,费解的开关
2,带分数
3,飞行员兄弟
4,翻硬币
1byte(字节) = 8bit(位),位指二进制位
float = int = 4byte = 32bit
char = 1byte = 8bit
double = long long = 8byte = 64bit
已知bite = B, bit = b
64MB ≈ 64 * 1e6(B),M是兆,1G = 1024M,M也是百万的缩写
= 64 * 2^20(B) ≈ 6.4 * 10^7(B) = 1.6 * 10^7(int),即 1600万个int
考虑到额外占用的空间,一般开到1500万,换算成2维数组就是a[3800][3800]
标签:递推,位运算,中等
95. 费解的开关 - AcWing题库
视频
AcWing 95. 费解的开关(蓝桥杯C++ AB组辅导课) - AcWing
题解
AcWing 95. 费解的开关 - AcWing
位运算(目录 -- biset)
STL入门 + 刷题(下)_千帐灯无此声的博客-CSDN博客
坑
注意将 step = 0 和 memcpy() 放在 for(int op...) 里,因为32个种op代表第一行的32种方案数,每个op都要重新备份,都有自己的step
当你每次结果都输出 -1 时,就应该想到 step 或者 ret 的问题
AC 代码
#include
#include //memcpy()
using namespace std;
#define N 6
char m[N][N], backup[N][N]; //m[][]表示亮或不亮
int tx[5] = {0,1,0,-1,0}, ty[5] = {1,0,-1,0,0}; //上下左右中, 漏了中间
void turn(int a, int b) //对(a, b)所在点和上下左右共5个点操作
{
for(int i = 0; i < 5; ++i) {
int aa = a + tx[i], bb = b + ty[i];
if(aa < 0 || aa > 4 || bb < 0 || bb > 4)
continue; //越界
//1^1 == 0, 0^1 == 1
m[aa][bb] ^= 1; //等价于m[aa][bb] = m[aa][bb] ^ 1
}
}
int main()
{
int t;
cin>>t;
while(t--) {
int ret = 10; //最小步数, 初始取尽可能的大
for(int i = 0; i < 5; ++i)
cin>>m[i]; //每一行按字符串输入到m[][], 表示亮或不亮
for(int op = 0; op < 32; ++op) { //op表示第 0 行 按 / 不按
//m拷贝到backup, 便于32组方案每组的恢复
memcpy(backup, m, sizeof(m));
int step = 0; //当前步数
//操作第1行
for(int i = 0; i < 5; ++i) //比如op是13, 对应01101, 1按, 0不按
if(op >> i & 1) { //右移 i 位 与 1 得到第 i 位的数字
step++;
turn(0, i); //第1行第i + 1列
}
//根据1234行操作2345行
for(int i = 0; i < 4; ++i) //1234行
for(int j = 0; j < 5; ++j)
if(m[i][j] == '0') { //主要是char
step++;
turn(i + 1, j);
}
//是否能全部亮
bool dark = false;
for(int j = 0; j < 5; ++j)
if(m[4][j] == '0') { //注意是char, 不要直接!
dark = true;
break;
}
if(!dark) ret = min(ret, step);
memcpy(m, backup, sizeof(m)); //不要忘记拷贝回去
}
if(ret <= 6) cout<
复杂度
由代码可知,32 * 25 * 5 * 500 = 2e6
1,第一行的 op 有32种方案
2,然后在32种方案的前提下,根据op对应的二进制对第一行操作,这里有5种
3,接着根据1~4行对2~5行操作,这里20种(5 + 20 = 25)
4,根据 turn() 函数,每个点要操作5次
5,最多500组测试
标签:递归,搜索,剪枝
1209. 带分数 - AcWing题库
解法 -- 暴力
暴力的2种解法都是3000多ms
(1)全排列打乱1~9的顺序
(2)cal()函数得到对应的整数,比如345896712这个排列里,取第1~4位就会得到4589
(3)两层for循环遍历(两个分割点),以便得到a, b, c对应的位数
(4)最后判断满足条件,将除法转化为乘法,避免C++对小数的处理
复杂度
全排列最坏复杂度 < n! * n,视作 n! * n,加上2层for循环枚举位数,相当于9个数里插2个隔板,即 = 28,已知 n = 9(9个数字全排列),
那么时间复杂度(运算次数) = 28 * 9! * 9 ≈ 1e8,刚好不超时
关于 next_permutation( 进入文章后,左侧目录跳转STL常用函数 第5个的next_permutation
STL入门 + 刷题(下)_千帐灯无此声的博客-CSDN博客
AC 代码1
全排列 -- next_permutation()
#include
#include // next_permutation()
using namespace std;
int num[9] = {1,2,3,4,5,6,7,8,9};
int cal(int i, int j) // 比如 i=3,j = 5, 全排列前返回345
{
int res = 0;
for (int x = i - 1; x < j; ++x)
res = res * 10 + num[x];
return res;
}
int main()
{
int target, a, b, c, ans = 0; // target = a + b/c
cin>>target;
do {
// 两层for枚举左右边界(a, b, c的位数)
for (int l = 1; l <= 7; ++l) // a=num[..l] b=num[l+1..r] c=num[r+1..]
for (int r = l + 1; r <=8; ++r) {
a = cal(1, l);
b = cal(l+1, r);
c = cal(r+1, 9);
if (target * c == a * c + b) // 除法转乘法
ans++;
}
}while(next_permutation(num, num + 9)); // 全排列打乱顺序
cout<
AC 代码2
dfs 深度优先搜索,手写全排列
#include
using namespace std;
int num[9], vis[9];
int ans = 0, a, b, c, target;
int cal(int i, int j) // 比如 i=3,j = 5, 全排列前返回345
{
int res = 0;
for (int x = i - 1; x < j; ++x)
res = res * 10 + num[x];
return res;
}
void dfsabc() // 分配位数
{
for (int l = 1; l <= 7; ++l)
for (int r = l + 1; r <= 8; ++r) {
a = cal(1, l);
b = cal(l + 1, r);
c = cal(r + 1, 9);
if (target * c == a * c + b)
ans++;
}
}
void dfs(int u) // dfs 全排列, u表示递归的深度(第u个数)
{
if (u > 9) {
dfsabc();
return;
}
for (int i = 0; i <= 8; ++i)
if(!vis[i]) { // 未访问过
num[u - 1] = i + 1; // u从1开始, i从0开始
vis[i] = 1; // 标记
dfs(u + 1); // 递归
vis[i] = 0; // 取消标记
}
}
int main()
{
cin>>target;
dfs(1);
cout<
解释下第35行
num[u - 1] = i + 1; // u从1开始, i从0开始
dfs(int u)里的u表示递归的深度,即第 u 位数,因为 u 从1开始
i 从 0 开始,所以 num[u - 1] = i + 1
标签:枚举,位运算
116. 飞行员兄弟 - AcWing题库
视频讲解
AcWing 116. 飞行员兄弟 - AcWing
暴力解法
暴力解法,分数组和二进制优化,2份AC代码,前者用数组存储棋盘,后者用二进制存储棋盘
解释
(1)每个位置最多操作1次(操作2次相当于没操作),最终结果与操作的顺序无关
(2)操作一个位置,所在的 行 和 列 全部反转
(3)采取暴力枚举:
一共16个开关,每个开关摁或不摁2种可能,所以是 2^16 种方案
每个方案有16个开关要遍历,采取位运算的话,对7个位置(同一行 / 列)只需要操作一次
所以时间复杂度是 2^16 * 16 = 2^20 ≈ 1e6
---- 当然,如果不用二进制优化,直接用数组存棋盘,复杂度 2^16 * (16 * 7 + 16 + 16) ----
---- 7表示每个开关共关联7个开关(同一行 / 列),两个16表示检查16个位置是否都开,以及都开的话,要记录结果 ----
(4)将4*4棋盘,按字符串读入4次,然后将读入的棋盘转化成一个整数(这个整数表示一个16位的二进制数),关于位运算
c++之位运算(详解,初学者绝对能看懂)_c++位运算_?!??的博客-CSDN博客
(5)常用技巧(判断一个二进制数 num 第 i 位是0还是1,第0位即低位第一位),只需要
num >> i & 1
(6)关于题目中的 “如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开”,只需要输出 0 ~ 2^16 - 1 里面字典序最小的方案(16位二进制中,1越靠前的方案,字典序越小),只需要从0开始遍历
常用技巧
位运算常用技巧:用一个整数存储一个矩阵或者一维数组的信息(整数对应的二进制)
思路
1,棋盘的16个数字读入字符数组 g[][]
2,枚举 0 ~ 2^16 - 1 这 2^16 种方案
3,每种方案,2层for循环遍历行 / 列,进行操作
补充
(1)
为什么2^16种操作数里,每种操作数表示一种操作方案呢
一个操作数,比如6,的二进制表示为0000 0000 0000 0110
又比如2^16-1,二进制表示为1111 1111 1111 1111
这里的1表示需要操作,即开关是关着的,需要被打开
(2)
数组存棋盘,将 + 和 - 字符读入char[][]里,同时,为了判断某一种操作方案16个位置,某个位置是否需要打开,想象棋盘为
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
只需要 操作数 << 棋盘数字,再 & 1,即可得到该位置是否需要打开
(3)
关于memcpy
std::memcpy - cppreference.com
// Defined in header
void* memcpy( void* dest, const void* src, std::size_t count ); 第 2 个参数,拷贝到第 1 个参数里,第 3 个参数为容器大小
(4)
关于pair
std::pair - cppreference.com
(5)
关于vector中operator=
std::vector
::operator= - cppreference.com (6)
关于for循环中的auto
Range-based for loop (since C++11) - cppreference.com
AC 数组
op存储操作方案,op对应的二进制中,1表示需要操作,g[][]存储初始状态
#include
#include
#include
#include // memcpy()
using namespace std;
#define x first
#define y second
typedef pair PII;
const int N = 5;
char g[N][N], backup[N][N]; // 存储棋盘,拷贝
vector ans; // 存储答案
int get(int x, int y)
{
return x * 4 + y; // 返回第x行第y列的数字0~15
}
void turn_one(int x, int y)
{
if (g[x][y] == '-') g[x][y] = '+';
else g[x][y] = '-'; // 改变一个开关
}
void turn_all(int x, int y) // 改变所有关联开关
{
for (int i = 0; i < 4; ++i) {
turn_one(x, i); // 列
turn_one(i, y); // 行
}
turn_one(x, y); // 翻转了2次,多了1次
}
int main()
{
// 读入棋盘
for (int i = 0; i < 4; ++i)
cin>>g[i]; // 读入一行字符
// 遍历2^16种操作(数)方案
for (int op = 0; op < 1 << 16; ++op) {
memcpy(backup, g, sizeof g); // 备份: g拷贝到backup
vector temp; // 存储当前所有操作
// 枚举当前操作方案的16个开关
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
if (op >> get(i, j) & 1) { // 需要翻转
temp.push_back({i, j}); // 插入操作
turn_all(i, j); // 按一下开关
}
// 检查是否全部打开
bool if_close = false;
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
if (g[i][j] == '+')
if_close = true;
if (!if_close && (ans.empty() || ans.size() > temp.size()))
ans = temp; // 更新最小切换次数
// 不要忘记备份回来, turn_all会修改原棋盘
memcpy(g, backup, sizeof g);
}
cout<
AC 二进制
#include
#include
using namespace std;
#define x first
#define y second
typedef pair PII;
int change[5][5];
int get(int x, int y)
{
return x * 4 + y;
}
int main()
{
int state = 0; // state 存储棋盘初始状态
// 二进制中1表示关,0表示开
// 读入
for (int i = 0; i < 4; ++i) {
string line; // 读入4行字符
cin>>line;
for (int j = 0; j < 4; ++j)
if (line[j] == '+') // 存下初始状态
state += 1 << get(i, j); // +表示对应二进制位上的1
}
// 预处理一个开关以及同一行/列的所有数
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j) {
for (int k = 0; k < 4; ++k) {
// 注意3个都是 [i][j]
change[i][j] += 1 << get(i, k); // 行
change[i][j] += 1 << get(k, j); // 列
}
change[i][j] -= 1 << get(i, j); // 减去重复部分
}
// 遍历
vector ans; // 存储答案的每一步操作
for (int op = 0; op < 1 << 16; ++op) { // op 存储要操作的开关
vector temp;
int now = state; // 初始状态拷贝
for (int i = 0; i < 16; ++i) // 遍历16个开关,对应二进制的16位
if (op >> i & 1) { // 这个开关需要操作
int x = i / 4, y = i % 4; // 得到行 列
now ^= change[x][y]; // 需要操作的数取反
temp.push_back({x, y}); // 记录操作每一步
}
if (!now && (ans.empty() || ans.size() > temp.size()))
ans = temp; // 满足更少操作次数
}
// 输出答案
cout<
1208. 翻硬币 - AcWing题库
标签:递推
思路
每次只改变相邻两个字符,由贪心可知,我们从第一个字符开始,如果第一个字符不一样,只能通过改变前2个字符来实现,由此可知,答案显然是唯一的
---- 从第一个字符遍历到最后,时间复杂度 O(n)
BUG
一开始,我用begin, end作为起始和目标字符串,报错,
error: reference to 'begin' is ambiguous
它与 C++ 标准库中的
begin
函数名产生了冲突将begin改为Begin即可
AC 代码
#include
using namespace std;
string Begin, End;
void change(int x)
{
Begin[x] = Begin[x] == '*' ? 'o' : '*'; // 三目运算符
Begin[x + 1] = Begin[x + 1] == '*' ? 'o' : '*';
}
int main()
{
cin>>Begin>>End;
int ans = 0;
for (int i = 0; i < Begin.size() - 1; ++i)
if (Begin[i] != End[i]) {
change(i);
ans++;
}
cout<