一.连通分量
无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。
输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.
输出格式:
1行,1个整数,表示所求连通分量的数目。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
2
解题思路
首先要了解连通分量的定义。连通分量:就是指子图里面的任意两个分量i和j都存在路径可以让i到j,j到i,而在本题我们讨论的是无向图,若是在有向图中,我们就要注意方向的问题,如果在有向图中依然满足任意两点连通,此时我们称为强连通分量。
总结:强连通分量存在于有向图,子图任意两个分量连通,如果原来不连通的分量,在有向图变成无向图后连通,称为弱连通分量
接下来我们以该图为例子,讲解思路。
法一
首先,我们可以很明显的发现,本图中存在两个连通分量,那么我们有两种思路,第一种就是我们都熟悉的DFS遍历,你只要是连通的分量,就必然会被遍历到,我们可以随便选取一个点,当进行完一次DFS后,一定意味着一个连通分量被打通了,我们只要用一个count来计数DFS的次数就可以啦,代码如下
#include<iostream>
#include<vector>
using namespace std;
int mark[50001];//标记
vector<int>apex[50001];//常规方法,用vector来建图,本题没有权重,所以直接用int
void dfs(int x) {
int i;
mark[x] = 1;
for (i = 0; i < apex[x].size(); i++) {
int next = apex[x][i];
if (mark[next] == 0)
dfs(next);
}
}
int main() {
int n, m,i,u,v;
int count = 0;
cin >> n >> m;
for (i = 1; i <= m; i++) {
cin >> u >> v;
apex[u].push_back(v);
apex[v].push_back(u);
}//存图,虽然是无向图,但是也要两边都存的,不然就会遍历不了
for (i = 1; i <= n; i++) {
if (mark[i] == 0) {
count++;
dfs(i);
}
}
cout << count;
return 0;
}
法二
法二的方法,也是我在考试的时候使用的方法,并查集的操作,相连通的分量就是在并查集中的一个子集,那么我们只要将连通的合并就好啦,当然,虽然本题限制不大,但我们也应当学会使用路径压缩和按秩分配来进行优化,代码如下
#include<iostream>
using namespace std;
int father[50001];//并查集的父亲
int find(int x) {
if (father[x]==x)return x;
return father[x] = find(father[x]);
}
void uni(int x, int y) {
if (find(x) == find(y))
return;
father[find(x)] = find(y);
return;
}
int main() {
int count = 0;
int n, m,i,u,v;
cin >> n >> m;
for (i = 1; i <= n; i++)
father[i] = i;
for (i = 1; i <= m; i++) {
cin >> u >> v;
uni(u, v);
}
for (i = 1; i <= n; i++) {
if (father[i] == i)
count++;
}
cout << count;
return 0;
}
二.旅行I
五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。将所有城市编号为1到n,你出发的城市编号是s。你想知道,到其它城市的最小费用分别是多少。如果可能,你想途中多旅行一些城市,在最小费用情况下,到各个城市的途中最多能经过多少城市。
输入格式:
第1行,3个整数n、m、s,用空格分隔,分别表示城市数、交通方式总数、出发城市编号, 1≤s≤n≤10000, 1≤m≤100000 。
第2到m+1行,每行三个整数u、v和w,用空格分隔,表示城市u和城市v的一种双向交通方式费用为w , 1≤w≤10000。
输出格式:
第1行,若干个整数Pi,用空格分隔,Pi表示s能到达的城市i的最小费用,1≤i≤n,按城市号递增顺序。
第2行,若干个整数Ci,Ci表示在最小费用情况下,s到城市i的最多经过的城市数,1≤i≤n,按城市号递增顺序。
输入样例:
在这里给出一组输入。例如:
5 5 1
1 2 2
1 4 5
2 3 4
3 5 7
4 5 8
输出样例:
在这里给出相应的输出。例如:
0 2 6 5 13
0 1 2 1 3
解题思路
非常常规的一题dijkstra的题目啦,相信你们一定都已经掌握了dijkstra算法,即求某一点到其他点的最短路,这题的特殊之处在于,求在满足到某点为最小路径的同时,经过的城市要最多。事实上,当我们回去思考dijkstra算法的时候,我们会发现,在求最短路径的时候,一定要进行一次松弛操作(就是两边之和大于第三边,我们取第三边才能最小),那么我们就可以用一个数组,存储每个城市最短路上的最多城市,在松弛条件下,进行更新,代码如下
#include<iostream>
#include<vector>
using namespace std;
int city[10001];//city[i]表示在i点的经过城市数量
int mark[10001];//标识城市有没有进入最小集合
int dist[10001];//到某点的最小距离
struct node {
int next;
int weight;
};
vector<node>apex[10001];//存储城市边集
void dij(int x,int n) {
int i;
for (i = 1; i <= n; i++) {
dist[i] = 20000;
mark[i] = 0;
city[i] = 0;
}//初始
dist[x] = 0;
for (i = 1; i < n; i++) {
int minest = 100000;
int v=1,j;
for (j = 1; j <= n; j++) {
if (dist[j] < minest&&mark[j]==0) {
minest = dist[j];
v = j;
}
}//找到最短城市
mark[v] = 1;
for (int u = 0; u < apex[v].size(); u++) {
int cost = apex[v][u].weight;//路径权
int seer = apex[v][u].next;//路径终点
if (dist[v] + cost < dist[seer]) {
dist[seer] = dist[v] + cost;
city[seer] = city[v] + 1;
}
if (dist[v] + cost == dist[seer]) {
if (city[v] + 1 > city[seer])
city[seer] = city[v] + 1;
}
}
}
}
int main() {
int n, m, s;
cin >> n >> m >> s;
node t;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
t.next = b;
t.weight = c;
apex[a].push_back(t);
t.next = a;
apex[b].push_back(t);
}//存储边集
dij(s, n);
for (int i = 1; i <= n; i++) {
if (i == 1)
cout << dist[i];
else
cout << " " << dist[i];
}
cout << endl;
for (int i = 1; i <= n; i++) {
if (i == 1)
cout << city[i];
else
cout << " " << city[i];
}
return 0;
}
三.算术表达式计算
任务: 计算算术表达式的值。
算术表达式按中缀给出,以=号结束,包括+,-,,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。
计算过程中,如果出现除数为0的情况,表达式的结果为”NaN” ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。
输入保证表达式正确。
输入格式:
一行,包括1个算术表达式。算术表达式的长度小于等于1000。
输出格式:
一行,算术表达式的值 。
输入样例:
在这里给出一组输入。例如:
(1+30)/3=
输出样例:
在这里给出相应的输出。例如:
10
解题思路
常规的表达式计算,相信大家都已经知道大致的思路了,下面我讲讲大致的思路,你可以用c++自带的vector或者stack建立栈(如果你想方便的,最好不要自己建栈),我们分为数字栈和操作符栈,当读入数字的时候,我们将数字压栈,那么当读入操作符的时候,我们就要根据操作符的优先级来进行操作,乘和除大于加和减,括号另作判断。当我们读入左括号时,直接压进去栈就好了,当我们没有读入左括号,那就要分两种情况了,读入正常操作符和右括号,第一种情况,我们要判断优先级,如果读入的操作符优先级小于栈顶,那么栈顶进行计算,否则压入,右括号的话,我们就要不停出栈。相信这么一大段文字你一定很难理解,下面我用上课的ppt来解释
偷个懒,代码如下
#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int>number;
vector<char>ope;
int prior(char x) {
if (x == '+' || x == '-')
return 1;
if (x == '*' || x == '/')
return 2;
return 0;
}
int ca(int x, char a, int y) {
if (a == '*')
return x * y;
else
if (a == '+')
return x + y;
else
if (a == '-')
return x - y;
else {
if (y == 0) {
cout << "NaN";
exit(0);
}
return x / y;
}
}
void cal(string s) {
int len = s.length();
s[len - 1] = '\0';
int i = 0;
while (i < len - 1) {
if (s[i] <= '9' && s[i] >= '0') {
int num = 0;
while (s[i] <= '9' && s[i] >= '0') {
num = num * 10 + s[i] - '0';
i++;
}
number.push_back(num);
}
else {
if (s[i] == '(') {
ope.push_back(s[i]);
}
else
if (s[i] != ')') {
while (!ope.empty() && prior(ope.back()) >= prior(s[i])) {
int a, b;
char ch;
a = number.back();
number.pop_back();
b = number.back();
number.pop_back();
ch = ope.back();
ope.pop_back();
number.push_back(ca(b, ch, a));
}
ope.push_back(s[i]);
}
else {
while (ope.back() != '(') {
int a, b;
char ch;
a = number.back();
number.pop_back();
b = number.back();
number.pop_back();
ch = ope.back();
ope.pop_back();
number.push_back(ca(b, ch, a));
}
ope.pop_back();
}
i++;
}
}
while (!ope.empty()) {
int a = number.back();
number.pop_back();
int b = number.back();
number.pop_back();
char ch = ope.back();
ope.pop_back();
number.push_back(ca(b, ch, a));
}
cout << number.back();
}
int main() {
string s;
cin >> s;
cal(s);
return 0;
}
四.序列乘积
两个递增序列A和B,长度都是n。令 Ai 和 Bj 做乘积,1≤i,j≤n.请输出n*n个乘积中从小到大的前n个。
输入格式:
第1行,1个整数n,表示序列的长度, 1≤n≤100000.
第2行,n个整数Ai,用空格分隔,表示序列A,1≤Ai≤40000,1≤i≤n.
第3行,n个整数Bi,用空格分隔,表示序列B,1≤Bi≤40000,1≤i≤n.
输出格式:
1行,n个整数,用空格分隔,表示序列乘积中的从小到大前n个。
输入样例:
在这里给出一组输入。例如:
5
1 3 5 7 9
2 4 6 8 10
输出样例:
在这里给出相应的输出。例如:
2 4 6 6 8
吐槽
考试的时候,因为时间不太够了,所以这题我就用暴力的办法了,最后拿了60分哈哈感觉不是很亏,下面给出暴力的思路。我们先假定一个长度为n的序列已经是最小的了,即a[0]*b[i](1<=i<=n),接下来用一个二重循环计算,用插入排序的思想,把数字插入,毫无疑问的会爆炸,代码如下
#include<iostream>
#include<cstdio>
using namespace std;
int a[100001], b[100001],c[100001];
int main()
{
int n,tt=0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
for (int i = 0; i < n; i++)
scanf("%d", &b[i]);
for (int i = 0; i < n; i++)
c[tt++] = a[0] * b[i];//先把原来的n个绝对最小的数字存进
for (int i = 1; i < n; i++)
{
for (int j= 0; j < n; j++)
{
int temp = 0;
temp = a[i] * b[j];
int end = n - 2;
int judge = 0;
while(temp < c[end+1])
{
c[end + 2] = c[end+1];
end--;
judge = 1;
}
if (judge)
c[end + 2] = temp;
if(judge==0&&j==0)
break;
}
}
e:
for (int i = 0; i < n; i++)
if (i == 0) printf("%d", c[i]);
else printf(" %d", c[i]);
printf("\n");
return 0;
}
正经的解题思路
a数组与b数组,我们先假设a数组的第一个数字乘b数组全部数字所得到的n个数字是目前的最小序列,然后再利用优先队列的思想进行排序,时间复杂度为nlogn。我们排序出一个矩阵,这个矩阵就是a数组乘b数组所得的n*n的矩阵,第一列就是我们计算出的目前假设最小序列,我们从中取出最小值,然后在最小值所处在的行取下一个值进去优先队列,如下图
代码如下
#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
long long a[100001], b[100001], c[100001];
struct node {
int row, j;
long long weight;
};
bool operator <(const node& a, const node& b) {
return a.weight > b.weight;
}
priority_queue<node> apex;
int main() {
int n, i;
node z;
scanf("%d", &n);
for (i = 0; i < n; ++i) {
scanf("%lld", &a[i]);
}
for (i = 0; i < n; ++i) {
scanf("%lld", &b[i]);
}
for (i = 0; i < n; ++i)
{
z.row = 0;
z.j = i;
z.weight = a[0] * b[i];
apex.push(z);
}
for (i = 0; i < n; i++) {
z = apex.top();
apex.pop();
c[i] = z.weight;
z.row++;
z.weight = a[z.row] * b[z.j];
apex.push(z);
}
for (i = 0; i < n; i++) {
if (i != n - 1)
printf("%lld ", c[i]);
else
printf("%lld", c[i]);
}
return 0;
}