集训周记 第四周

本周编程学习笔记:

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 (详见上),这样就显得很清爽,不用再写一些罗里吧嗦有的没的的了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值