7-1 连通分量
原题呈现:
解题思路 : 考虑使用dfs。从第1个点开始做dfs,将所有途经的点利用flag数组做标记。以此类推考虑第2、3…n个点,如果该点被标记过,跳过,否则从该点进行dfs。
以下是根据这一思路写出代码:
#include<bits/stdc++.h>
#define N 50005
using namespace std;
int num;
vector<int> edges[N]; // vector存图
int flag[N];
void add(int u, int v)// 简单加边
{
edges[u].push_back(v);
}
void dfs(int now) {
for (int i = 0 ; i < edges[now].size(); i++) { // 遍历每一条边
if (flag[edges[now][i]] != 1) {
flag[edges[now][i]] = 1;
dfs(edges[now][i]);
}
}
}
int main() {
int n, m, u, v;
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d",&u,&v);
add(u, v);
add(v, u); // 双向加边
}
for (int i = 1; i <= n; i++) {
if (!flag[i]) {
dfs(i);
num++;
}
}
printf("%d", num);
return 0;
}
解后反思: 当时大脑短路对边进行标记而没有对点,导致没有AC。此外,注意无向图中一条边的存储需要添加两条有向边。
7-2 整数拆分
原题呈现:
解题思路 : 用递归写dfs,参数re表示剩余的加数的和,参数t表示当前是第几个加数,当re为0时检查当前是否已经是第(k+1)个加数(即前k个加数和为n)。
以下是根据这一思路写出代码:
#include <bits/stdc++.h>
using namespace std;
int res[51];
int num = 0;
int n, k;
void dfs(int re, int t)
{
if (re == 0 && t != k + 1)
return;
if (re == 0 && t == k + 1)
{
num++;
for (int i = 1; i <= k; ++i)
{
printf("%d", res[i]);
if (i != k)
printf(" ");
else
printf("\n");
}
return;
}
for (int i = res[t - 1]; i <= re; ++i)
{
res[t] = i;
dfs(re - i, t + 1);
}
}
int main()
{
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; ++i)
{
res[1] = i;
dfs(n - i, 2);
}
printf("%d\n",num);
}
解后反思: 注意拆分方案不能重复,因而保持加数从左至右不减。
7-3 数字变换
原题呈现:
解题思路 : 因为要求变换的最小步数而非所有可能的变换,所以该题是隐藏的bfs问题而非dfs问题。开一个数组a保存每个数是由哪个数得到的,这样从起点x做bfs,直到出现能在下一步得到终点y的数即可。
以下是根据这一思路写出的代码:
#include <bits/stdc++.h>
using namespace std;
int a[100001], num = 1, x, y, flag=0;
stack <int> s;
queue <int> q;
void bfs(int i) {
q.push(i);
while (1) {
i = q.front();
q.pop();
if ((i + 1) <= 100000 && a[i + 1] == 0) {
a[i + 1] = i;
q.push(i + 1);
if (i + 1 == y) {
flag = 1;
return;
}
}
if ((i * 2) <= 100000 && a[i * 2] == 0) {
a[i * 2] = i;
q.push(i * 2);
if (i * 2 == y) {
flag = 1;
return;
}
}
if (i > 1 && (i - 1) <= 100000 && a[i - 1] == 0) {
a[i - 1] = i;
q.push(i - 1);
if (i - 1 == y) {
flag = 1;
return;
}
}
}
}
int main() {
scanf("%d %d", &x, &y);
a[x] = x;
if (x > y) {
printf("%d\n", x - y);
for (int i = x - 1; i >= y; i--) {
printf("%d", i);
if (i != y)
printf(" ");
}
}
else if (x == y)
printf("0\n");
else {
bfs(x);
int tmp = y;
while (tmp != x) {
s.push(tmp);
tmp = a[tmp];
}
int len = s.size();
printf("%d\n",len);
for (int i = 0; i < len; i++) {
printf("%d", s.top());
s.pop();
if (i != len - 1)
printf(" ");
}
}
}
解后反思: 1.注意特判x<y和x=y的情景。2.注意题中所给的三种变换有优先次序,因而对i做bfs时,按i+1,i*2,i-1的先后顺序入队。
7-4 序列乘积
原题呈现:
解题思路1 : dijkstra算法+堆优化 该算法无法处理边权为0或负的情况
以下是根据这一思路写出的代码:
#include<bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[100010], to[200010], nex[200010], v[200010], tl, num[100010];
long long d[100010];
bool b[100010];
void add(int x, int y, int w) {
to[++tl] = y; // f到y的路径是输入中的第几条
v[tl] = w;
nex[tl] = fr[x]; //上一条从x出发的路径
fr[x] = tl; //fr中存从x出发的路径是输入中的第几条
}
priority_queue< pair<long long, int> > q;//先比较第一个元素大小,相同再比第二个
int main() {
int n, m, x, y, z, s;
scanf("%d %d %d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
for (int i = 1; i <= n; i++)
d[i] = 1e10;//用maxn初始化
d[s] = 0;
q.push(M(0, s));
while (!q.empty()) {
int x = q.top().second;
q.pop();
if (b[x])//判断该点是否被使用过
continue;
b[x] = 1;
for (int i = fr[x]; i; i = nex[i]) {
int y = to[i], l = v[i];
if (d[y] > d[x] + l) {
d[y] = d[x] + l;
num[y] = num[x] + 1;
q.push(M(-d[y], y));//懒得重载运算符&&以便于每次找最近的点
}
else if (d[y] == d[x] + l) {//因为要经过尽可能多的城市,所以取等的情况也要考虑
d[y] = d[x] + l;
if (num[x] + 1 > num[y])
num[y] = num[x] + 1;
q.push(M(-d[y], y));
}
}
}
for (int i = 1; i <= n; i++) {
printf("%lld", d[i]);
if (i != n)
printf(" ");
}
printf("\n");
for (int i = 1; i <= n; i++) {
printf("%d", num[i]);
if (i != n)
printf(" ");
}
return 0;
}
解题思路2 : Bellman-Ford算法+基本优化
以下是根据这一思路写出的代码:
#include <bits/stdc++.h>
using namespace std;
long long dis[10010];
int u[200100], v[200100], w[200100], num[10010], n, m, s, check;//我们定义一个check,优化用
const int inf = 2147483647;
int main()
{
cin >> n >> m >> s;//输入
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &u[i], &v[i], &w[i]);//读入边
u[m + i] = v[i];
v[m + i] = u[i];
w[m + i] = w[i];
}
m=m*2;
for (int i = 1; i <= n; i++)
dis[i] = inf;//dis数组初始化
dis[s] = 0;
for (int k = 1; k <= n - 1; k++)
{
check = 0;//check归零
for (int i = 1; i <= m; i++)
{
if (dis[v[i]] > dis[u[i]] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
num[v[i]] = num[u[i]] + 1;
check = 1;//如果dis数值改变,check赋值为1
}
else if (dis[v[i]] == dis[u[i]] + w[i])
{
if(num[v[i]] < num[u[i]] + 1)
num[v[i]] = num[u[i]] + 1;
}
}
if (check == 0)
break;//如果没变,直接跳出循环,不要浪费时间
}
for (int i = 1; i <= n; i++) {
printf("%lld", dis[i]);
if (i != n)
printf(" ");
}
printf("\n");
for (int i = 1; i <= n; i++) {
printf("%d", num[i]);
if (i != n)
printf(" ");
}
}
解后反思: 1.无向图中一条边的存储需要添加两条有向边。 2.做松弛时如果发现两者相等,如果贸然改变/不改路线可能会使经过的点数变少,因而需要分情况讨论。