🏆🏆🏆🏆🏆🏆🏆
欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录)
文章字体风格:
红色文字表示:重难点✔★
蓝色文字表示:思路以及想法✔★
如果大家觉得有帮助的话,感谢大家帮忙
点赞!收藏!转发!
我的qq号是:1210931886,欢迎大家加群,一起学习,互相交流,共同进步🎉🎉✨✨
🥇🥇🥇🥇🥇🥇🥇
蓝桥杯系列,为大家提供
- 做题全集,备战蓝桥杯,就做这个系列的题即可
- 一个大概的做题规划——大家最好在此基础上提前两个月准备
备战蓝桥杯就刷这些题
第一天博客链接
第二天博客链接
每次遍历dfs参数是 遍历的坑位
原题链接
#include
using namespace std;
const int N = 10;
int path[N];//保存序列
int state[N];//数字是否被用过
int n;
void dfs(int u)//u表示的是 第几个坑位
{if(u > n)//数字填完了,输出{for(int i = 1; i <= n; i++)//输出方案cout << path[i] << " ";cout << endl;}for(int i = 1; i <= n; i++)//空位上可以选择的数字为:1 ~ n{if(!state[i])//如果数字 i 没有被用过{path[u] = i;//放入空位state[i] = 1;//数字被用,修改状态dfs(u + 1);//填下一个位state[i] = 0;//回溯,取出 i}}
}int main()
{cin >> n;dfs(1);
}
原题链接
方法 1. 按行遍历(过程中有回溯、剪枝)
思想:
- 每次递归中,遍历一行的元素,如果可以放皇后,就递归到下一行,下一行中不行了,就返回来,回溯,
//cpp
#include
using namespace std;const int N = 11;char q[N][N];//存储棋盘
bool dg[N * 2], udg[N * 2], cor[N];
//点对应的两个斜线以及列上是否有皇后int n;void dfs(int r)
{if(r == n)//放满了棋盘,输出棋盘{for(int i = 0; i < n; i++){for(int j = 0; j < n; j++)cout << q[i][j];cout << endl;}cout << endl;return;}for(int i = 0; i < n; i++)//第 r 行,第 i 列 是否放皇后{if(!cor[i] && !dg[i + r] && !udg[n - i + r])//不冲突,放皇后{q[r][i] = 'Q';cor[i] = dg[i + r] = udg[n - i + r] = 1;//对应的 列, 斜线 状态改变dfs(r + 1);//处理下一行cor[i] = dg[i + r] = udg[n - i + r] = 0;//恢复现场q[r][i] = '.';}}
}int main()
{cin >> n;for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )q[i][j] = '.';dfs(0);return 0;
}
方法2. 按每个元素遍历(没有减枝)
// 不同搜索顺序 时间复杂度不同 所以搜索顺序很重要!
#include
using namespace std;
const int N = 20;int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N];
// 因为是一个个搜索,所以加了row// s表示已经放上去的皇后个数
void dfs(int x, int y, int s)
{// 处理超出边界的情况if (y == n) y = 0, x ++ ;if (x == n) { // x==n说明已经枚举完n^2个位置了if (s == n) { // s==n说明成功放上去了n个皇后for (int i = 0; i < n; i ++ ) puts(g[i]);puts("");}return;}//和上面按行遍历的差别就是,这里没有循环// 分支1:放皇后if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) {g[x][y] = 'Q';row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;dfs(x, y + 1, s + 1);row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;g[x][y] = '.';}// 分支2:不放皇后dfs(x, y + 1, s);
}int main() {cin >> n;for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )g[i][j] = '.';dfs(0, 0, 0);return 0;
}
- bfs 需要队列
- ==走过的点标记上距离(既可以记录距离,也可以判断是否走过)==没走过的置为-1
- (队列存储一个节点pair
)
原题链接
原题链接
#include
#includeusing namespace std;int n,m;
int p[110][110];
int d[110][110];
int dx[4] = {-1,0,1,0};
int dy[4] = {0,-1,0,1};pair q[110*110];
int hh,tt=-1;void bfs()
{q[++tt] = {1,1};p[1][1] = 1;d[1][1] = 0;while(hh<=tt){pair item = q[hh++];for(int i = 0; i < 4; i++){int x = item.first;int y = item.second;//cout << x << ' ' << y << endl;if(p[x+dx[i]][y+dy[i]]!=1 && x+dx[i]<=n && x+dx[i] > 0 && y+dy[i] <=m && y+dy[i] >0){d[x+dx[i]][y+dy[i]] = d[x][y] + 1;q[++tt] = {x+dx[i],y+dy[i]};p[x+dx[i]][y+dy[i]] = 1;}}}
}int main()
{cin >> n >> m;for(int i = 1; i<=n; i++){for(int j = 1; j<=m; j++)cin >> p[i][j];}bfs();cout << d[n][m];return 0;
}
原题链接
- 字符串存储二维数组
- 每种情况对应一个移动距离,正好就用map
- 字符串有find函数,map有count函数
- map的count就可以判别 该位置是否走过
补充一点就是,一种情况只能走过一次!不可能走走走地出现与之前一样的情况,这不就是白走了嘛- 先swap才能接着判断
#include
#include
#include
#includeusing namespace std;int bfs(string s)
{queue q;q.push(s);int dx[4] = { 1,0,-1,0 },dy[4] = { 0,-1,0,1 };unordered_map d;d[s] = 0;while (!q.empty()){string f = q.front();q.pop();if (f == "12345678x")return d[f];int df = d[f];int k = f.find("x");int x = k / 3;int y = k % 3;for (int i = 0; i < 4; i++){if (x + dx[i] <= 2 && x + dx[i] >= 0 && y + dy[i] <= 2 && y + dy[i] >= 0){swap(f[k], f[(x + dx[i]) * 3 + y + dy[i]]);if (!d.count(f)){d[f] = df + 1;q.push(f);}swap(f[k], f[(x + dx[i]) * 3 + y + dy[i]]);}}}return -1;
}int main()
{char ch;string s;for (int i = 0; i < 9; i++){cin >> ch;s += ch;}cout << bfs(s);return 0;
}
原题链接
- 无向图的节点数 是 2倍
- 无向图的连接
void add(int a,int b)
{e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
- 遍历过的点标记一下,不再遍历,因为无向图可能往回遍历
#include
#includeusing namespace std;const int N = 2e5 + 10;int h[N], e[N], ne[N], idx;
bool st[N];int sum;
int n;
int res;void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int q = 0;
int dfs(int x)
{if (x == -1)return 0;st[x] = true;int xx = 0;int xxx = 0;for (int i = h[x]; i != -1; i = ne[i]){if (st[e[i]] == false){int s = dfs(e[i]);xx = max(s, xx);xxx += s;}}//cout << e[x] << "下面的值" << xxx << endl;sum = max(xx, n - xxx - 1);if (sum < res){res = sum;q = x;}return xxx + 1;}int main()
{memset(h, -1, sizeof h);cin >> n;int a, b;while (cin >> a >> b)add(a, b), add(b, a);res = n;dfs(1);cout << res;return 0;
}
原题链接
原题链接
#include
#include
#includeusing namespace std;const int N = 1e5 + 10;int e[N], ne[N], h[N], idx;
int d[N];
bool st[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}void bfs(int x)
{queue q;q.push(x);while (q.size()){int u = q.front();q.pop();st[u] = true;int dd = d[u];for (int i = h[u]; i != -1; i = ne[i]){//这里可以改进,因为可以用d 判断是否遍历过该节点if (st[e[i]] == false){st[e[i]] = true;q.push(e[i]);d[e[i]] = min(d[e[i]], dd + 1);}}}}int main()
{memset(h, -1, sizeof h);memset(d, 0x3f, sizeof d);d[1] = 0;int n, k;cin >> n >> k;for (int i = 1; i <= k; i++){int a, b;cin >> a >> b;add(a, b);}bfs(1);if(d[n] != 0x3f3f3f3f)cout << d[n];elsecout << -1;return 0;
}
- 拓扑是一个宽搜
- 遍历顺序是 度为0(可能有多个为0的)
- 可以用q[] 表示队列,这样就用一个队列就可以存储拓扑的结果和 遍历的过程了(也就是拓扑排序的遍历过程,就是答案顺序)
原题链接
#include
#include
#include
using namespace std;const int N=100010;
int h[N],e[N],ne[N],idx;
int n,m;
int q[N],d[N];//q表示队列,d表示点的入度void add(int a,int b)
{e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}bool topsort()
{int hh=0,tt=-1;for(int i=1;i<=n;i++)if(!d[i]) q[++tt]=i;//将入度为零的点入队while(hh<=tt){int t=q[hh++];for(int i=h[t];i!=-1;i=ne[i]){int j=e[i];d[j]--;//删除点t指向点j的边if(d[j]==0)//如果点j的入度为零了,就将点j入队q[++tt]=j;}}return tt==n-1;//表示如果n个点都入队了话,那么该图为拓扑图,返回true,否则返回false
}int main()
{cin>>n>>m;memset(h,-1,sizeof(h));//如果程序时间溢出,就是没有加上这一句for(int i=0;iint a,b;scanf("%d%d",&a,&b);add(a,b);//因为是a指向b,所以b点的入度要加1d[b]++;}if(topsort()) {for(int i=0;i
#include
#include
#includeusing namespace std;const int N = 1e5+10;bool st[N];
int e[N],ne[N],idx,h[N];
int b[N];//每个节点的入读int n,k; queue q,ans;void bfs()
{while(q.size()){int f = q.front();q.pop();for(int i = h[f]; i != -1; i = ne[i]){b[e[i]]--;if(b[e[i]]==0){ans.push(e[i]);q.push(e[i]);}}}}void add(int a,int b)
{e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}int main()
{ memset(h,-1,sizeof h);cin >> n >> k;for(int i = 1; i <= k; i++){int a,bb;cin >> a >> bb;add(a,bb);b[bb]++;}for(int i = 1; i <= n; i++){if(b[i]==0){//cout << i << endl;q.push(i);ans.push(i);}}bfs();if(ans.size()!=n){cout << -1;return 0;}//cout << ans.size() << endl;while(ans.size()){cout << ans.front() << ' ';ans.pop();}return 0;
}
原题链接
刷题总结
- 稀疏矩阵 和 疏密矩阵(疏密矩阵 可以用 邻接矩阵存储方式)
- 邻接矩阵直接就可以存储 边距离了
- d存储到1节点的距离
- dijk就是两步 :1. 在dist里选出最小值 2.遍历dist更新
如何选(如下)
int t = -1;
for (int j = 1; j <= n; j ++ )if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;
原题链接
- 利用小根堆存储,元素是PII
#include
#include
#include
using namespace std;typedef pair PII;const int N = 1e6+10;int e[N],ne[N],h[N],idx;
int w[N];
int d[N];
bool st[N];int n,m;void add(int a,int b,int c)
{e[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx++;
}void dijkstra()
{priority_queue, greater> heap;d[1] = 0;heap.push({0,1});while(heap.size()){auto item = heap.top();heap.pop();if(st[item.second]==true)continue;st[item.second]=true;for(int i = h[item.second]; i!= -1; i = ne[i]){if(d[e[i]] > w[i] + item.first){d[e[i]] = w[i] + item.first;heap.push({d[e[i]],e[i]});}}}
}int main()
{memset(d,0x3f,sizeof d);memset(h,-1,sizeof h);cin >> n >> m;for(int i = 0; i < m; i++){int a,b,c;cin >> a >> b >> c;add(a,b,c);}dijkstra();if(d[n]!=0x3f3f3f3f)cout << d[n];elsecout << -1;return 0;
}
int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{int a, b, w;
}edges[M];// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。for (int i = 0; i < n; i ++ ){for (int j = 0; j < m; j ++ ){int a = edges[j].a, b = edges[j].b, w = edges[j].w;if (dist[b] > dist[a] + w)dist[b] = dist[a] + w;}}if (dist[n] > 0x3f3f3f3f / 2) return -1;return dist[n];
}
原题链接
- dijkstra不能处理 负权边
- 由于有k的限制,所以每次遍历一条边的时候,需要用备份数据,因为可能会发生数据改变(这样可能导致一次遍历所有边直接到n点了)
- 有可能出现n节点连接的是负边,那么我们就让n节点的值 > 0x3f3f3f3f/2 这样就可以了
#include
#includeusing namespace std;const int N = 510, M = 10010;struct Edge {int a;int b;int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边int bellman_ford() {memset(dist, 0x3f, sizeof dist);dist[1] = 0;for (int i = 0; i < k; i++) {//k次循环memcpy(back, dist, sizeof dist);for (int j = 0; j < m; j++) {//遍历所有边int a = e[j].a, b = e[j].b, w = e[j].w;dist[b] = min(dist[b], back[a] + w);//使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来}}if (dist[n] > 0x3f3f3f3f / 2) return -1;else return dist[n];}int main() {scanf("%d%d%d", &n, &m, &k);for (int i = 0; i < m; i++) {int a, b, w;scanf("%d%d%d", &a, &b, &w);e[i] = {a, b, w};}int res = bellman_ford();if (res == -1) puts("impossible");else cout << res;return 0;
}
原题链接
- 和宽搜差不多,只是可能会 返回走(但距离值更新了,就把这个节点入队列再处理一次)
#include
#include
#includeusing namespace std;const int N = 1e5+10;
int e[N],ne[N],h[N],idx;
int w[N];bool st[N];int n,m;void add(int x,int y,int c)
{e[idx] = y,ne[idx] = h[x],w[idx] = c,h[x] = idx++;
}int d[N];void spfa()
{memset(d,0x3f,sizeof d);d[1] = 0;queue q;q.push(1);st[1] = true;while(q.size()){auto f = q.front();q.pop();st[f] = false;for(int i = h[f]; i != -1; i = ne[i]){int j = i;if(d[e[j]] > d[f] + w[j]){d[e[j]] = d[f] + w[j];if(st[e[j]]==false){q.push(e[j]);st[e[j]] = true;}}}}
}int main()
{cin >> n >> m;memset(h,-1,sizeof h);for(int i = 0; i < m; i++){int x,y,c;cin >> x >> y >> c;add(x,y,c);}spfa();if(d[n] == 0x3f3f3f3f)cout << "impossible";elsecout << d[n];return 0;
}
原题链接
图1才叫负环
图2不是负环
出现负环会怎么样
但出现负环的时候,如果我们要去求1到n的最短路,那么过程中,一定会在这个负环中一直转圈,导致路程可以变为负无穷
怎么判断图中是否有负环?
综上,我们就采取求最小路径的方式(但是本题不是求最短路),当我们求最短路径的过程中,发现有一段路径重复走,那么就说明一定出现了负环
问题来了:怎么判断某段路径在重复走
我们想,1到n号点 最多才可能走了n-1条边
如果我们发现 到某点时 已经走了 大于等于n条边,那么一定就是有负环了
由于我们不知道 1 到 x点最多可能有多少条边,但一定不会超过 n - 1 条边,所以我们就都用 大于等于n条边去判断
原题链接
#include
#include
#include
#include using namespace std;const int N = 2010, M = 10010;int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}bool spfa()
{queue q;for (int i = 1; i <= n; i ++ ){st[i] = true;q.push(i);}while (q.size()){int t = q.front();q.pop();st[t] = false;for (int i = h[t]; i != -1; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];cnt[j] = cnt[t] + 1;if (cnt[j] >= n) return true;if (!st[j]){q.push(j);st[j] = true;}}}}return false;
}int main()
{scanf("%d%d", &n, &m);memset(h, -1, sizeof h);while (m -- ){int a, b, c;scanf("%d%d%d", &a, &b, &c);add(a, b, c);}if (spfa()) puts("Yes");else puts("No");return 0;
}
- e,ne,h,idx 用于存储边,所以数值应该与边一样多
- 把所有点都入队列,防止不是连通图
- dist里存储多少都可以,因为我们只需判断负权回路
- 当一个点所走的路径长度大于n,那么就一定有负边,因为最多就是n正常的话。
- 一定要有st数组,判断是否再走这个点
原题链接
- 用二维数组存储更方便
- 读入存储的时候,读取最小值,并且到自身值为0
- Floyd
#include
#includeusing namespace std;
int n,m,k;
const int N = 210;
int d[N][N];void Floyd()
{for(int k = 1; k <= n; k++)for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}int main()
{cin >> n >> m >> k;memset(d,0x3f,sizeof d);for(int i = 0; i < m; i++){int x,y,c;cin >> x >> y >> c;d[x][y] = min(d[x][y], c);d[x][x] = 0;d[y][y] = 0;}Floyd();for(int i = 0; i < k; i++){int x,y;cin >> x >> y;if(d[x][y]>=0x3f3f3f3f/2)cout << "impossible" << endl;elsecout << d[x][y] << endl;}return 0;
}
原题链接
- prim算法的思路:选出每个最小边且该节点没有走过。
关键开辟一个集合存储每个点的所遍历过的最小边的值,(有个前提就是,需要一个点一个点地走,所以有了prim算法)- 自己到自己的距离也应是0x3f,因为d存储的是自己的最短边
- 图存储的 都是最小值
#include
#includeusing namespace std;const int N = 510;
int p[N][N], d[N];
int n, m;
bool st[N];
int res;
int sum;void prim()
{memset(d, 0x3f, sizeof d);d[1] = 0;for (int k = 0; k < n; k++){int t = -1;for (int l = 1; l <= n; l++){if (st[l] == false && (t == -1 || d[t] > d[l]))t = l;}if (d[t] == 0x3f3f3f3f){res++;return;}st[t] = true;sum += d[t];for (int i = 1; i <= n; i++){d[i] = min(d[i], p[t][i]);}}
}int main()
{memset(p,0x3f,sizeof p);cin >> n >> m;for (int i = 0; i < m; i++){int x, y, c;cin >> x >> y >> c;p[x][y]= p[y][x] = min(p[x][y],c);}prim();if (res)cout << "impossible";elsecout << sum;return 0;
}
下一篇:家庭网络WIFI相关知识