我的算法修炼之路--6 ——模幂、构造、背包、贪心、剪枝、堆维护六题精析


💗博主介绍:计算机专业的一枚大学生 来自重庆 @燃于AC之乐✌专注于C++技术栈,算法,竞赛领域,技术学习和项目实战✌💗

💗根据博主的学习进度更新(可能不及时)

💗后续更新主要内容:C语言,数据结构,C++、linux(系统编程和网络编程)、MySQL、Redis、QT、Python、Git、爬虫、数据可视化、小程序、AI大模型接入,C++实战项目与学习分享。

👇🏻 精彩专栏 推荐订阅👇🏻

点击进入🌌作者专栏🌌:
算法画解
C++

🌟算法相关题目点击即可进入实操🌟

感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!

文章目录

前言

这些题目摘录于洛谷,好题,典型的题,考察各类算法运用,可用于蓝桥杯及各类算法比赛备战,算法题目练习,提高算法能力,补充知识,提升思维。

锻炼解题思路,从学会算法模板后,会分析,用到具体的题目上。

对应题目点链接即可做。

本期涉及算法: 数学(可取模的快速幂),构造(图),01背包(问题转化),贪心算法,dfs暴搜+剪枝,模拟+小根堆维护最小值

题目清单

1.转圈游戏

题目: P1965 [NOIP 2013 提高组] 转圈游戏


解法:数学 + 快速幂

n个数一圈(循环),计算(x + m * 10^k)% n的值即为移动后的位置。

由于 0 < k < 10^9, 就是10(109),非常的大,需要用到可以取模的快速幂,一边乘一边取模。用long long来存。

代码:

cpp 复制代码
#include <iostream>
using namespace std;

typedef long long LL;
LL n, m, k, x;

LL qpow(LL a, LL b, LL p)
{
	LL ret = 1;
	while(b)
	{
		if(b & 1) ret = ret * a % p;
		b >>= 1;
		a = a * a % p; 
	}
	return ret;
}

int main() 
{
	cin >> n >> m >> k >> x;
	cout << (x + m * qpow(10, k, n)) % n << endl;
	return 0;
}

2.System Administrator(系统管理员)

题目: CF22C System Administrator

解法:构造

要满足题目要求:去掉一个点后,图不连通,那么就构造一个点v左边连一个点,右边连n - 2个点。因为这道题目的边有限,且要用完,这种方式连的边最多,且满足题目要求。

重要的性质:

n个点如果至少要连n - 1条边, 所以m < n - 1时,连通图就不存在。所以 m > n - 1;

当有一个顶点与v相连,并且与其他顶点都不相连时,最大的边数可由以下方法来求:对于一个无向图,n个顶点,最多有n(n - 1) / 2条边。(结论),因为每一个点可以和剩下的n-1个点连一条边,那么就是n(n - 1), 且因为两个点只有一条无向边,会多算一次就/2。综上,可以算满足题目要求的情况:一个点连左边一个点,边数1,这个点加上右边一共n - 1个点边数(n - 1) * (n - 2) / 2 + 1。所以 m < (n - 1) * (n - 2) / 2 + 1。

综上所述,n - 1 < m < (n - 1) * (n - 2) / 2 + 1,可以用于判断m是否合法。

如果m在范围内,那么图存在,一定能构造出来,可以先在v的左边放置一个顶点w,在另一边放剩余的n-2个点,将所有的点(除了v本身),连在v上,然后将它们(除了w)相互连接起来。

代码:

cpp 复制代码
#include <iostream>
using namespace std;

typedef long long LL;
LL n, m, v;

int main() 
{
	cin >> n >> m >> v;
	if(m < n - 1 || m > (n -1) * (n - 2) / 2 + 1)
	{
		cout << -1 << endl;
		return 0;
	 } 
	 
	 for(int i = 1; i <= n; i++)
	 {
	 	if(i != v)
	 	{
	 		cout << i << " " << v << endl; //v点和其它点相连
			 m--; 
		 }
	 }
	 
	 int w = 1;
	 if(w == v) w = 2; //选一个不是v的点
	 
	 for(int i = 1; i <= n; i++)
	 {
	 	for(int j = 1; j < i; j++) //i与j不同,只连一次
		{
			 if(!m) return 0;
		    if(i == v || i == w || j == v || j == w) continue;
		   cout << i << " " << j << endl;
		   m--;
		}
	  } 
	  
	return 0;
}

3.多米诺骨牌

题目: P1282 多米诺骨牌

解法:动态规划(01背包)

不要去背模板,这道题目的难点在于问题的转化 和对二维有负数无法对应到数组下标的特殊处理方式。

思考:

复杂问题(变化值多)简单化: 首先这道题有上下两个值,而且还会不断翻转,如果将两个的更改都考虑进去会很麻烦,回想道之前的类比方法:固定一个值之后,改变另外一个,但是这里固定一个后,就无法翻转,翻转后两个值都发生变化,很难更新处理。那没我们可以局部分开算上下的差值,那么两个值的变化转变为一个值得变化,且翻转就是去这个差值的相反数,非常的巧妙。

对于每一个骨牌,设上面的点数是x,下面的点数是y:

如果不旋转,对于总差值的贡献就是x - y;

如果旋转,对于总差值的贡献就是 y - x = -(x - y);

问题转化: 对于每个骨牌,我们统计一下上下点数的差值a [i ],然后的操作就是要么旋转,要么不旋转 ,最终想看看总和最接近0(也就是最小,因为不一定为0)的值的最小旋转次数,正好对应01 背包问题

但是,此时会有一个问题,就是我们的总和有可能是负数对应不到f表里面的下标 。对于这种问题,我们常见的处理操作就是统一将第二维加上一个偏移量,相当于将整个f表向右平移,直到所有的下标都是正数。

本题的偏移量可以设置成5000,因为假如1 - 6 = -5,n最大为1000,最小值不会低于−5000。

1.状态表示:

f[i] [j]表示:考虑前i个骨牌,确定好它们的状态之后,上下差值正好为j时,此时的最小旋转次数。

最终结果就是f[n] [j] 中,状态合法,从小到大找 ,也就是j的绝对值最小时的值

2.状态转移方程:

对于i位置的骨牌,我们有不旋转或者旋转两种状态:

a.不旋转:此时i 位置骨牌对上下差值的贡献就是a [i ],那么就需要去**[1, i− 1]** 中凑总和为 ja [i] 的最小旋转次数,即f[i - 1] [j - a[i]];

b.旋转:此时i 位置骨牌对上下差值的贡献就是−a [i ],那么就需要去**[1, i − 1]** 中凑总和为 j + a [i] 的最小旋转次数再加上本次旋转,即f[i - 1] [j + a[i];

综上, f[i] [j] 应该为上面两种情况的最小值

3.初始化:

先把所有位置初始化为正无穷大0x3f3f3f3f,用于后面取min,然后把f[0] [0]初始化为0(合理)。

4.填表顺序:

从上往下每一行,每一行从左往右。

4.排队接水

题目: P1223 排队接水

解法:贪心

题目要求:要使所有人的平均等待时间最短,可以想到贪心:让接水时间短的先接水,这样后面的人等待的时间更短,接水时间长的排后面。用一个变量wait来记录等待接水的时间,不断更新等待时间和总时间。

代码:

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e3 + 10;
int n;
struct node
{
	int t, id;
}a[N];

bool cmp(node& x, node& y)
{
	return x.t < y.t;
}

int main() 
{
	cin >> n;
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i].t;
		a[i].id = i;
	}
	
	sort(a + 1, a + 1 + n, cmp);
	double sum = 0, wait = 0;
	for(int i = 1; i <= n; i++)
	{
		cout << a[i].id << " ";
		sum += wait;
		wait += a[i].t;
	}
	
	cout << endl;
	printf("%.2lf\n", sum / n);
	return 0;
}

5.健康的荷斯坦奶牛

题目: P1460 [USACO2.1] 健康的荷斯坦奶牛 Healthy Holsteins

解法:dfs暴搜+剪枝

从前往后考虑每一个饲料,对于当前饲料要么选,要么不选,dfs 决策树可以把所有情况枚举出来。如果从小到大考虑,而且最终结果天然是按照字典序排列的。

剪枝:
最优性剪枝: 如果当前选择的饲料总数"大于等于"之前已经搜到过的最优解,减掉;

如果当前选择的饲料已经满足要求,判断是否是最优解之后,减掉。

细节,每次要记得恢复现场 。 用一个path记录当前走到哪里来了,将path置为1,path |= (1 << pos), 将path置为0,path &= ~(1<<pos);

代码:

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 30;
int n, m;
int v[N];
int g[N][N];

int cnt; //记录当前选了多少个
int path; //记录选了哪些饲料

int ret = N; //记录最少选了
int st;

//当前的决策是path是否满足奶牛的需要
bool check()
{
	for(int i = 1; i <= n; i++)
	{
		int sum = 0;
		for(int j = 1; j <= m; j++)
		{
			if((path >> j) & 1) sum += g[j][i];
		}
		if(sum < v[i]) return false;
	 } 
	 return true;
 } 

void dfs(int pos)
{
	//最优性剪枝
	if(cnt >= ret) return;
	if(check())
	{
		ret = cnt;
		st = path;
		return;
	 } 
	 
	 if(pos > m) return;
	 
	 //选
	 cnt++;
	 path |= (1 << pos); //置为1
	 dfs(pos + 1);
	 cnt--;
	 path &= ~(1 << pos);
	 
	 //不选
	 dfs(pos + 1); 
}

int main() 
{
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> v[i];
	cin >> m;
	for(int i = 1; i <= m; i++)
	   for(int j = 1; j <= n; j++)
	   cin >> g[i][j];
	   
	   dfs(1);
	   
	   cout << ret << " ";
	   for(int i = 1; i <= m; i++)
	   {
	   	if((st >> i) & 1) cout << i << " ";
	   }
	   
	return 0;
}

6.接水问题

题目: P1190 [NOIP 2010 普及组] 接水问题

解题思路:模拟,小根堆

维护m个水龙头的结束时间,取其最小值,考虑到用小根堆维护,min出堆,加入学生序列,将学生接水量 + min,进堆;同时记录max即为结果。

代码:

cpp 复制代码
//模拟,小根堆,同时找max
#include <iostream>
#include <queue>
#include <vector> 
using namespace std;

int n, m;

int main()
{
	cin >> n >> m;
	priority_queue<int, vector<int>, greater<int>> heap;
	for(int i = 1; i <= m; i++) //初始化m个水龙头的结束时间,方便后面n次,不用提前存 
	 heap.push(0); 
	 
	 int ret = 0;
	 for(int i = 1; i <= n; i++)  //维护m个水龙头的结束时间
	{
		 int w; cin >> w; 
	    int t = heap.top(); heap.pop(); //min
	    t += w; //累积起来 
	    heap.push(t); //加入维护 
	    ret = max(ret, t);
	}
	cout << ret << endl; 
	return 0;
}

加油!志同道合的人会看到同一片风景。

看到这里请点个赞关注 ,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!

相关推荐
NAGNIP9 小时前
一文搞懂树模型与集成模型
算法·面试
NAGNIP10 小时前
万字长文!一文搞懂监督学习中的分类模型!
算法·面试
技术狂人16810 小时前
工业大模型工程化部署实战!4 卡 L40S 高可用集群(动态资源调度 + 监控告警 + 国产化适配)
人工智能·算法·面试·职场和发展·vllm
D_FW10 小时前
数据结构第六章:图
数据结构·算法
a程序小傲10 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
HellowAmy11 小时前
我的C++规范 - 玩一个小游戏
开发语言·c++·代码规范
自学不成才11 小时前
深度复盘:一次flutter应用基于内存取证的黑盒加密破解实录并完善算法推理助手
c++·python·算法·数据挖掘
June`11 小时前
全排列与子集算法精解
算法·leetcode·深度优先