

💗博主介绍:计算机专业的一枚大学生 来自重庆 @燃于AC之乐✌专注于C++技术栈,算法,竞赛领域,技术学习和项目实战✌💗
💗根据博主的学习进度更新(可能不及时)
💗后续更新主要内容:C语言,数据结构,C++、linux(系统编程和网络编程)、MySQL、Redis、QT、Python、Git、爬虫、数据可视化、小程序、AI大模型接入,C++实战项目与学习分享。
👇🏻 精彩专栏 推荐订阅👇🏻
🌟算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!
文章目录
- 前言
- 题目清单
-
- 1.转圈游戏
- [2.System Administrator(系统管理员)](#2.System Administrator(系统管理员))
- 3.多米诺骨牌
- 4.排队接水
- 5.健康的荷斯坦奶牛
- 6.接水问题
前言
这些题目摘录于洛谷,好题,典型的题,考察各类算法运用,可用于蓝桥杯及各类算法比赛备战,算法题目练习,提高算法能力,补充知识,提升思维。
锻炼解题思路,从学会算法模板后,会分析,用到具体的题目上。
对应题目点链接即可做。
本期涉及算法: 数学(可取模的快速幂),构造(图),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]** 中凑总和为 j − a [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;
}

加油!志同道合的人会看到同一片风景。
看到这里请点个赞 ,关注 ,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!