本周编程学习笔记:
1.快速幂(用了二进制和位运算符相关的知识)
【二进制与位运算符相关知识】
(要运行那一块的时候解开相关注释即可)
#define _CRT_SECURE_NO_WARNINGS 1
//二进制
//位运算符(优先级 & ^ |)
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-4;
//快速幂
int qsm(int a, int b/*, int mod*/) {
int ans = 1;
while (b) {
if (b & 1) {
ans *= a;
// ans %= mod;
}
a = a * a;
// a %= mod;
b >>= 1;
}
return ans;
}
signed main() {
<<左移 *2
//cout << (6 << 1) << endl;
//cout << (6 << 2) << endl;
>>右移 /2
//cout << (6 >> 1) << endl;
//cout << (6 >> 2) << endl;
判断奇偶 使用&
//int n; cin >> n;
//if (n & 1) {
// cout << "YES" << endl;
//}
//else {
// cout << "NO" << endl;
//}
判断第几位的奇偶
//int n; cin >> n;
//if (n >> 2 & 1) {//第三位
// cout << "YES" << endl;
//}
//else {
// cout << "NO" << endl;
//}
//pow 用二进制优化
cout << qsm(2, 6) << endl;
cout << pow(2, 6) << endl;
return 0;
}
【快速幂】
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-4;
int qsm(int a, int b, int mod) {
int ans = 1;
while (b) {
if (b & 1) {
ans *= a;
ans %= mod;
}
a = a * a;
a %= mod;
b >>= 1;
}
return ans;
}
signed main() {
int a, b, p; cin >> a >> b >> p;
printf("%d^%d mod %d=%d", a, b, p, qsm(a, b, p));
return 0;
}
a6 == a(21) * a(22)
a(20) * a(20) == a(21)
a(21) * a(21) == a(22)
a(22) * a(22) == a(2**3)
(有点类似“前缀和”的感觉,“累加”起来就行了)
2.洛谷P1873(二分练习题)
#define _CRT_SECURE_NO_WARNINGS 1
#include<cstdio>
long long n, m, high[1000000 + 10];//本蒟蒻全部用了long long qwq
long long tmp, left, right;
int main()
{
scanf("%lld%lld", &n, &m);//注意读入输出要使用lld
for (long long i = 1; i <= n; i++) {
scanf("%lld", &high[i]);
if (high[i] > right) right = high[i];//找到最大的high[i]做为right
}
while (left <= right) {
int mid = (left + right) / 2;
//printf("%d ",mid);
tmp = 0;
for (int i = 1; i <= n; i++)
if (high[i] > mid) tmp += high[i] - mid;
if (tmp < m) right = mid - 1;
else left = mid + 1;
}
printf("%lld", right);//输出最终结果
return 0;//不加return 0 结果return WA;
}
/*
while(left<=right){//二分模板
int mid=(left+right)>>1;//位运算貌似会节省时间(与除以2等价)
tmp=0;//记得更新
for(int i=1;i<=n;i++)
if(high[i]>mid) tmp+=high[i]-mid;
if(tmp<m) right=mid-1;
else left=mid+1;//模板不提
}
*/
我自己写的那个有一个地方过不了,这个是洛谷上一位大佬的题解(我就当再学会一种模板了)
3.洛谷P1678(二分练习题)
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-4;
//洛谷 P1678
int s[maxn];
int s2[maxn];
signed main() {
int m, n; cin >> m >> n;
for (int i = 1; i <= m; i++) {
cin >> s[i];
}
sort(s + 1, s + 1 + m);
int sum = 0;
for (int i = 1; i <= n; i++) {
cin >> s2[i];
}
for (int i = 1; i <= n; i++) {
if (s2[i] <= s[1]) {
sum += fabs(s2[i] - s[1]); continue;
}
else if (s2[i] >= s[m]) {
sum += fabs(s2[i] - s[m]); continue;
}
int l = 0, r = m;
while (l < r) {
int mid = (l + r) >> 1;
if (s[mid] <= s2[i]) {
l = mid + 1;
}
else {
r = mid;
}
}
sum += min(abs(s[l - 1] - s2[i]), abs(s[l] - s2[i]));
}
cout << sum;
return 0;
}
这是二分的另一种模板,也是我平常喜欢的模板,在这里记录一下
4.Codeforces Round 964 (Div. 4)E题
【超时代码】
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//int s[maxn][2] = {0};
struct x {
int x1 = 0, x2 = 0;
};
x s[maxn];
bool cmp(x e1, x e2) {
if (e1.x2 == e2.x2) {
return e1.x1 > e2.x1;
}
return e1.x2 < e2.x2;
}
signed main() {
int t; cin >> t;
while (t--) {
int l, r; cin >> l >> r;
for (int i = l; i <= r; i++) {
s[i].x1 = i;
s[i].x2 = i / 3 + 1;
}
sort(s + l, s + r, cmp);
int cnt = 0;
while (s[l].x1 != 0) {
s[l].x1 /= 3;
s[l + 1].x1 *= 3;
cnt++;
}
for (int i = l + 1; i <= r; i++) {
while (s[i].x1 != 0) {
s[i].x1 /= 3;
cnt++;
}
}
cout << cnt << endl;
}
return 0;
}
我刚开始是以为是在一直除以3那边太久了超时了,所以打比赛的时候想要用二分+快速幂来解决,没想到根本不是(别问为什么,因为用了也超时🤡),后来学长上课讲题目的时候说只要把相关数据预处理即可
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#define double long double
const int maxn = 1e5 + 100;
const double eps = 1e-4;
//
【next_permutation】
///*
//next_permutation 是 C++ 标准库算法中的一个函数,它用于重新排列给定范围内的元素,
//以生成该排列的下一个字典序排列。如果给定的排列已经是字典序上最大的排列,
//那么函数会将其重新排列为最小的排列(即升序排列),并返回 false 以指示没有下一个排列;否则,它返回 true。
//*/
//
//signed main() {
// string s = "acb";
// do {
// cout << s << endl;
// } while (next_permutation(s.begin(), s.end()));//降序排列枚举?But 首字符升序(说错了,是字典序)
// return 0;
//}
//
signed main() {
int a[] = { 1,2,3,4,5 };
do{
for (int i = 0; i <= 4; i++) {
cout << a[i] <<" \n"[i == 4];//这个写法好
}
} while (next_permutation(a + 0, a + 5));
}
5.使用next_permutation进行全排列
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#define double long double
const int maxn = 1e5 + 100;
const double eps = 1e-4;
//
【next_permutation】
///*
//next_permutation 是 C++ 标准库算法中的一个函数,它用于重新排列给定范围内的元素,
//以生成该排列的下一个字典序排列。如果给定的排列已经是字典序上最大的排列,
//那么函数会将其重新排列为最小的排列(即升序排列),并返回 false 以指示没有下一个排列;否则,它返回 true。
//*/
//
//signed main() {
// string s = "acb";
// do {
// cout << s << endl;
// } while (next_permutation(s.begin(), s.end()));//降序排列枚举?But 首字符升序(说错了,是字典序)
// return 0;
//}
//
signed main() {
int a[] = { 1,2,3,4,5 };
do{
for (int i = 0; i <= 4; i++) {
cout << a[i] <<" \n"[i == 4];//这个写法好
}
} while (next_permutation(a + 0, a + 5));
}
这是一种按照字典序的较为方便的全排列方法,在我们学到dfs深搜之前还是嘎嘎好用的,虽然还是有他的不足之处(详见代码块)
请注意:如果说你要进行next_permutataion 的那组数据不是升序的,记得先sort一下,否则排列出来的情况会不全
6.dfs深搜与其相关的基础知识
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
//深搜
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//void dfs(int x) {
// if (x == 0) {
// return;
// }
// //往下搜索
// cout << x << endl;
// dfs(x - 1);
// //往上回溯
// cout << x << endl;
//}
//
//signed main() {
// dfs(3);
// return 0;
//}
int vis[10010];
int n;
void dfs(vector<int> g,int cnt) {
if (cnt == n) {
for (int i = 0; i < n; i++) {
cout << g[i] << " ";
}
cout << endl;
return;
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
g.push_back(i);
vis[i] = 1;
dfs(g, cnt + 1);
g.pop_back();
vis[i] = 0;
}
return;
}
signed main() {
cin >> n;
dfs({},0);
return 0;
}
总的来说就是类似于
dfs{
如果 满足什么什么条件 -> 进行相关处理 -> 退出
否则
向下搜索
dfs
向上回溯
}
差不多这种感觉
7.搜索算法 —— N皇后问题
N皇后问题大家应该都知道吧,我就不再多加赘述了:
【判断有N个皇后时有几种可能】
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
const int maxlen = 10;
int n;//N <= maxlen;
int vis[maxlen][maxlen] = { 0 };
int cnt = 0;
//输出有多少种可能
void doAdd(int r, int c, int n, int val) {
if (r < 0 || r >= n) {
return;
}
if (c < 0 || c >= n) {
return;
}
vis[r][c] += val;
}
void add(int r, int c, int n, int val) {//这种二维打标记的方法真的很nice
int i;
for (int i = 0; i < n; i++) {
doAdd(r, i, n, val);
doAdd(i, c, n, val);
}
for (int i = 0; i < n; i++) {
doAdd(r + i, c + i, n, val);
doAdd(r - i, c - i, n, val);
doAdd(r + i, c - i, n, val);
doAdd(r - i, c + i, n, val);
}
}
void dfs(int dep, int maxdep) {
int i;
if (dep == maxdep) {
cnt++;
return;
}
for (i = 0; i < maxdep; i++) {
if (vis[dep][i] == 0) {
add(dep, i, maxdep, 1);
dfs(dep + 1, maxdep);
add(dep, i, maxdep, -1);
}
}
}
signed main() {
cin >> n;
dfs(0, n);
cout << cnt;
}
我这段代码其实和6.里面的差不多,只是他那个是用 vis 一维 打标记,我这个是用 vis 进行二维打标记
其实大可不必像二维打标记这样这么麻烦 其实我们可以从 x+y x-y 入手(这是两条斜线)
【有N个皇后是,每组的皇后的列坐标】
#define _CRT_SECURE_NO_WARNINGS 1
//#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<cstdlib>
#include<stdio.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
const int maxlen = 11;
int n;//N <= maxlen;
vector<int> tm;//存储皇后的列坐标
//输出每种可能下的每个皇后的列坐标
int vis[maxlen][maxlen] = { 0 };
int cnt = 0;
int CNT = 0;
int flag = 0;
void doAdd(int r, int c, int n, int val) {
if (r < 0 || r >= n) {
return;
}
if (c < 0 || c >= n) {
return;
}
vis[r][c] += val;
}
void add(int r, int c, int n, int val) {//这种二维打标记的方法真的很nice
int i;
for (int i = 0; i < n; i++) {
doAdd(r, i, n, val);
doAdd(i, c, n, val);
}
for (int i = 0; i < n; i++) {
doAdd(r + i, c + i, n, val);
doAdd(r - i, c - i, n, val);
doAdd(r + i, c - i, n, val);
doAdd(r - i, c + i, n, val);
}
}
void dfs(int dep, int maxdep) {
int i;
if (dep == maxdep) {
cnt++;
if (!tm.empty()) {
for (auto x : tm) {
printf("%5d", x);
}
flag = 1;
cout << endl;
}
return;
}
for (i = 0; i < maxdep; i++) {
if (vis[dep][i] == 0) {
tm.push_back(i + 1);
add(dep, i, maxdep, 1);
dfs(dep + 1, maxdep);
tm.pop_back();
add(dep, i, maxdep, -1);
}
}
}
signed main() {
cin >> n;
dfs(0, n);
if (flag == 0) {
cout << "no solute!" << endl;
}
//cout << cnt;
}
跟上面那个差不多,我无非就是在深搜的时候,将满足条件的皇后的列坐标给存到了 vector 里,然后在回溯的时候将其吐出来即可(记得在满足条件是时进行打印输出)
8.搜索算法 —— 算24点(doubles) (这个比较难,比较逆天,主要是我太菜了)
算24点问题大家应该都玩过或者听说过吧,我们这就选择 4 个小于等于10的正整数,然他们通过加减乘除,最终得到24即可,如果可以的话则输出“Yes”否则输出“No”
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
int ok;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-6;
void dfs(vector<double> ans) {
if (ans.size() == 1) {
if (fabs(ans[0] - 24.000000) < eps) {
ok = 1;
}
return;
}
vector<double> ret = ans;
//先随机挑选两个
for (int i = 0; i < ans.size(); i++) {
for (int j = i + 1; j < ans.size(); j++) {
double x = ans[i], y = ans[j];
//为了避免动态规划问题(为了保护脑子),我选择了创建临时副本
vector<double> ret = ans;
ret.erase(ret.begin() + i);
ret.erase(ret.begin() + j - 1);
ret.push_back(x + y);
dfs(ret);
ret.pop_back();
ret.push_back(x - y);
dfs(ret);
ret.pop_back();
ret.push_back(y - x);
dfs(ret);
ret.pop_back();
ret.push_back(x * y);
dfs(ret);
ret.pop_back();
if (y != 0) {
ret.push_back(x / y);
dfs(ret);
ret.pop_back();
}
if (x != 0) {
ret.push_back(y / x);
dfs(ret);
ret.pop_back();
}
}
}
}
signed main() {
int t; cin >> t;
while (t--) {
ok = 0;
vector<double> g;
for (int i = 1; i <= 4; i++) {
double x; cin >> x; g.push_back(x);
}
dfs(g);
if (ok == 1) {
cout << "Yes" << endl;
}
else {
cout << "No" << endl;
}
}
return 0;
}
思路就是先把要进行运算的4个数先丢到数组里面,每次都随机选择两个(pop出来),然后让他们两进行四则运算(运算结果再push回去),这点想通就行了,有几个坑再注意一下就行了(详见我的代码)
那个还有再补充一下,为了避免动态规划的问题(因为我在把两个数pop出来之后,回溯的时候并没有将他本体再push回去),所以我搞了一个临时的副本ret(主要是想省脑子)
9.搜索算法 —— 拆分自然数
问题:
任何一个大于1的自然数n(n <= 10),总可以拆分成若干个小于n的自然数之和。
当n=7共14种拆分方法:
7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
咱们把这些可能都打印出来即可
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
int n;
void dfs(int x, int mx, vector<int>ans) {
if (x == n) {
if (ans.size() <= 1) {
return;
}
for (int i = 0; i < (int)(ans.size()) - 1; i++) {
cout << ans[i] << "+";
}
cout << ans[ans.size() - 1] << "\n";
return;
}
for (int i = 1; i <= n; i++) {
if (x + i > n || i < mx) continue;
ans.push_back(i);
dfs(x + i, i, ans);
ans.pop_back();
}
}
int main()
{
cin >> n;
dfs(0, 0, {});
}
记得要将 vector ans 放入 dfs 中,我之前把他搞成了全局变量(脑子被炮打了),就没有办法在深搜中迭代了,
这个是我犯过的错误,切记切记
10.走迷宫问题
4连通迷宫和8连通迷宫大差不差,无非就是多了4个方向
我们下面就写一个8连通的(改改数据就变成4连通的了)
问题:
给定一个M*M(2≤M≤9)的迷宫,迷宫用0表示通路,1表示围墙。
迷宫的入口和出口分别位于左上角和右上角,入口和出口显然都是0。
在迷宫中移动可以沿着上、下、左、右、左上、右上、左下、右下八个方向进行,前进格子中数字为0时表示可以通过,为1时表示围墙不可通过,需要另外再找找路径。
请统计入口到出口的所有路径(不重复),并输出路径总数。若从入口无法到达出口,请输出0。
输入:
第一行输入1个正整数M(≤M≤9),表示迷宫是M行M列。
第2行到第n+1行是一个M阶的0-1方阵。
输出:
统计入口到出口的所有路径(不重复),并输出路径总数。若从入口无法到达出口,请输出0。
样例输入:
3
0 0 0
1 0 1
0 0 1
样例输出:
4
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//8连走迷宫 from schoolACM
//迷宫的入口和出口分别位于左上角和右上角
int a[1000][1000], vis[1000][1000];
int n, ans;
int dx[] = { 0,0,1,-1,1,1,-1,-1 };
int dy[] = { 1,-1,0,0,1,-1,1,-1 };
void dfs(int x, int y) {
if (x == 1 && y == n) {
ans++; return;
}
for (int i = 0; i < 8; i++) {
int tx = x + dx[i];
int ty = y + dy[i];
if (tx<1 || tx >n || ty <1 || ty > n || vis[tx][ty] == 1 || a[tx][ty] == 1) continue;
vis[tx][ty] = 1;
dfs(tx, ty);
vis[tx][ty] = 0;
}
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
}
}
vis[1][1] = 1;
dfs(1, 1);
cout << ans << endl;
return 0;
}
用一个数组来存储迷宫的地形,另一个数字来标记已经走过的路线,由于你已经标记过了你走过的路线了,你遇到了死胡同的时候会自己出来。但是深度搜索不同于广搜,他并不会的出最短的可行路线(广搜的话下周再学啦~)
学长也搞了一个巧妙地模拟在迷宫之中移动地方法,就是搞了两个数组 dx 和 dy (详见上),这样就显得很清爽,不用再写一些罗里吧嗦有的没的的了