机试指南 cha 5 图论
并查集
畅通工程
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
using namespace std;
/*
并查集
畅通工程:所有顶点之间均可连通
time : 20+20+20
*/
int findroot(int v[],int a)
{
// 寻找根节点的过程中,如果不是根节点的直接孩子,则修改为其直接孩子
if (v[a] == -1)
return a;
else
{
int tmp = findroot(v,v[a]);
v[a] = tmp;
return tmp;
}
}
int main()
{
int m,n,x1,x2;
int i,l,r;
int v[1001] = {0}; // v[i] 代表第i个顶点对应的双亲节点
while (cin >> n >> m)
{
if (n == 0)
break;
for (int i = 1;i<=n;i++)
v[i] = -1;
for (i = 0 ;i < m;i++)
{
cin >> x1 >> x2;
// 根节点不同,即不在一个集合,则连通
l = findroot(v,x1);
r = findroot(v,x2);
if (l!=r)
v[r] = l;
}
// 输入完了以后,相连的结点会在同一个集合中,计算有几个并查集,并查集-1即为新增路径条数
int count = 0 ;
for (i = 1;i<=n;i++)
{
if (v[i] == -1)
count ++;
}
cout << count -1 << endl;
}
return 0;
}
最小生成树
还是畅通工程
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>
using namespace std;
/*
最小生成树
time :
*/
int v[1010];
int findroot(int a)
{
if (v[a] == -1)
return a;
else
{
int tmp = findroot(v[a]);
// 非根节点a直接和根相连
v[a] = tmp;
return tmp;
}
}
struct node
{
int a;
int b;
int w;
};
bool cmp(const node &a,const node &b)
{
return a.w < b.w; // 从小到大
}
int main()
{
int n,a,b,w;
int i,l,r;
node node[5000],tmp;
int ans = 0;
while (cin >>n && n!=0)
{
ans = 0;
for (int i = 1;i<=n;i++)
v[i] = -1;
// 初始化node数组
for (i = 0;i< n*(n-1)/2 ;i++)
{
cin >> a >>b >> w;
node[i].a =a;
node[i].b =b;
node[i].w =w;
}
sort(node,node+n*(n-1)/2,cmp); // 不是n条边!
for (i = 0;i<n*(n-1)/2;i++)
{
tmp = node[i];
l = findroot(tmp.a);
r = findroot(tmp.b);
if (l!=r)
{
v[r] = l; // 两个根节点集合合并
ans += tmp.w;
}
}
cout << ans << endl;
}
return 0;
}
最短路径
最短路
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#define INFINITY 65515
using namespace std;
/*
最短路径
time :
*/
int main()
{
int i,j,n,m;
int p[101][101];
while (cin >> n >> m && (n!=0 && m != 0))
{
for (i = 1;i<=n;i++)
for (j=1;j<=n;j++)
{
if (i == j)
p[i][i] = 0;
else
p[i][j] = INFINITY;
}
for (i = 0 ;i < m;i++)
{
int a,b,c;
cin >> a>> b >> c;
p[a][b] = c;
p[b][a] = c;
}
for (int k = 1 ; k <= n; k++)
{
for ( i = 1;i<=n ; i++)
for (j = 1 ;j<=n ; j++)
if ( i!=k && j!= k && (p[i][k] + p[k][j] < p[i][j]))
{
p[i][j] = p[i][k] + p[k][j];
}
}
cout << p[1][n] <<endl;
}
return 0;
}
more is better
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>
using namespace std;
// 求并查集的过程中在每个集合根节点中记录该集合的人数
const int num = 100001;
int v[num]; // v[i] 为第i个节点的双亲节点
int sum[num];// sum[i]当且仅当v[i] == -1 时有效,数字代表该集合的人数
int findroot(int a)
{
if (v[a] == -1)
return a;
else
{
int tmp = findroot(v[a]); // 递归得到根节点
v[a] = tmp;
return tmp;
}
}
int main()
{
int n;
int a,b,i,r,l;
while ( cin>>n )
{
for (i = 1;i<= num ; i++) // 这个地方不能设为2*n,因为如果n很大时,2*n可能会超过num
{
v[i] = -1; // 只知道N对好朋友,但不知道男孩的数目,最多为一对儿两个不同的男孩
sum[i] = 1;
}
for (i = 1 ;i <= n ;i++)
{
cin >> a >> b;
l = findroot(a);
r = findroot(b);
if (l!=r)
{
v[l] = r;
sum[r] += sum[l] ;// 注意!合并集合时要把两个集合的sum值合并
}
}
int max = 1;
for (i = 1;i<=num;i++)
{
if (v[i] == -1 && sum[i] > max)
{
max = sum[i];
}
}
cout << max <<endl;
}
return 0;
}
####Freckles
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int num = 101;
int v[num]; // 点集合
struct edge
{
int v1,v2; // 每条边的两个端点均在v[num]中
float w;
}e[num*(num-1)/2]; // 边集合
int findroot(int a)
{
if (v[a] == -1)
return a;
else
{
int tmp = findroot(v[a]); // 递归得到根节点
v[a] = tmp;
return tmp;
}
}
bool cmp(const edge &a,const edge &b)
{
return a.w < b.w;
}
int main()
{
int n,i,j,l,r;
float dot[num][2],a,b;
float ans;
while (cin>>n)
{
for (i=1;i<=n;i++)
{
v[i] = -1;
cin >> dot[i][0] >> dot[i][1] ;
}
// 初始化边edge数组,n个点有n(n-1)/2条边,进行二层循环
float ans;
int size = 1;
for (i = 1 ;i <= n ;i++)
{
for (j = i+1 ; j <= n ; j++)
{
a = dot[i][0] - dot[j][0];
b = dot[i][1] - dot[j][1];
ans = sqrt(a*a + b*b);
e[size].v1 = i;
e[size].v2 = j;
e[size++].w = ans;
}
}
// sort(edge),合并并查集
sort(e+1,e+size,cmp);
// for (i = 1 ;i<4;i++)
// cout << e[i].w << endl;
ans = 0;
// 从小权值边开始选择,每次要判断这条边的两个端点是否在同一个集合中
for (i = 1;i<size;i++)
{
l = findroot(e[i].v1);
r = findroot(e[i].v2);
if (l!=r)
{
v[l] = r;
ans += e[i].w;
}
}
printf("%.2f",ans);
}
return 0;
}
拓扑排序
legal or not
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;
struct edge
{
int nextnode;
};
queue<int> q;
vector<edge> vex[100];
int visited[100];
void findrudu(int n)
{
// 找到入度为0的点(未在邻接表右侧链中出现的点),放到队列中
int i,a;
bool rudu[100];
for (i = 0;i<100;i++)
rudu[i] = true;
// 一个再细微不过的知识点,都有可能成为决定成败的事情!
for (i = 0;i<n;i++)
{
for (vector<edge>::iterator it = vex[i].begin() ; it != vex[i].end() ; it++)
{
a = (*it).nextnode;
rudu[a] = false;
}
}
for (i = 0;i<n;i++)
{
// cout << rudu[i] <<' ' << visited[i] << endl;
if (rudu[i]&&visited[i]){
q.push(i);
}
}
}
int main()
{
int n,m,i,a,b;
edge e;
vector<edge>::iterator it;
// i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点
while (cin >> n >> m)
{
if (m==0&&n==0)
break;
// 初始化vector
for (i = 0;i<n;i++)
{
vex[i].clear();
visited[i] = true;
}
// 初始化队列
while (!q.empty())
q.pop();
for (i = 0;i<m;i++)
{
cin >> a >> b; // a->b为一条弧
e.nextnode = b;
vex[a].push_back(e);
}
findrudu(n);
while (!q.empty())
{
a = q.front();// 从队列中取出一个入度为0的结点
q.pop();
vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的弧
visited[a] = false;
findrudu(n);
}
// 如果队列为空时仍旧有顶点为true,则存在环
bool flag = true;
for (i = 0;i<n;i++ )
if (visited[i])
{
flag = false;
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0 ;
}
太蠢的做法!结果超时了,完全可以第一次统计每个结点的入度,然后每次删除节点时对该入度数组进行-- 操作,用bool真的太蠢了!而且这道题墨迹了一整个下午! 以后半小时内做不出来就研究答案!一道题目不要超过1个半小时!
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;
queue<int> q;
vector<int> vex[100];
int main()
{
int n,m,i,a,b;
vector<int>::iterator it;
int degree[100];
// i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点
while (cin >> n >> m)
{
if (m==0&&n==0)
break;
// 初始化vector和degree数组
for (i = 0;i<n;i++)
{
vex[i].clear();
degree[i] = 0;
}
// 初始化队列
while (!q.empty())
q.pop();
for (i = 0;i<m;i++)
{
cin >> a >> b; // a->b为一条弧
vex[a].push_back(b);
// 增加b的入度
degree[b] ++;
}
for (i = 0;i<n;i++)
{
if (degree[i] == 0)
q.push(i);
}
int c = 0; // 计数器,用来确定已经被删除的结点的个数,或者用bool数组,但bool数组还得遍历
while (!q.empty())
{
a = q.front();// 从队列中取出队首
q.pop(); // 删除队首
c ++;
for (it = vex[a].begin();it != vex[a].end() ; it++)
{
// 将待删除节点的邻接点的入度--,如果该邻接点成为入度为0的点,入队
degree[*it] -- ;
if (degree[*it] == 0)
q.push(*it);
}
vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的邻接顶点
}
if (c == n)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0 ;
}
- 另一种遍历vector的方法
- for (int i = 0;i<vector.size();i++)
- vector[i]获取元素
有的东西不是你不会,而是你学过之后没有总结积累教训,反复地练习自己的思维弊端,然后修补不对的反射,所以你不停地出错,不停地出错,却不知道在绊倒的地方做个记号告诉自己不能再次摔倒了。要建立复盘总结机制。
确定比赛名次 30min
和上面那道题比起来,只需要每次pop()之前或之后输出结点数据。而且并不需要erase掉被选中结点的链表,因为我们看的是被push进queue的结点。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;
vector<int> vex[501];
struct cmp{
bool operator() (const int &a, const int& b ){
return a > b;
}
};
priority_queue<int, vector<int>, cmp > q;
int degree[501];
// 如何保证号小的先被选中?优先队列!
int main()
{
int n,m,i,a,b;
while (cin>>n>>m)
{
// 初始化
for (i=1;i<=n;i++)
{
vex[i].clear();
degree[i] = 0;
}
// queue 没有 clear()方法,故需要循环pop()数据
while (!q.empty())
q.pop();
for (i = 0;i<m;i++)
{
cin >> a >> b;
vex[a].push_back(b);
degree[b]++;
}
for (i=1;i<=n;i++)
if (degree[i] == 0)
q.push(i);
bool flag = true;
while (!q.empty())
{
a = q.top();// 选取队尾元素
q.pop();
if (flag)
{
flag = false;
cout << a;
}else
cout << ' ' << a;
for (i=0;i<vex[a].size();i++)
{
b = vex[a][i];
if ((--degree[b])==0)
q.push(b);
}
}
cout << endl;
}
return 0 ;
}
产生冠军
没调过,已经调了一个小时了,附带上正确答案
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <map>
#include <string>
using namespace std;
vector<int> vex[10000];
int degree[10000];
queue<int> q;
int main()
{
int i,n,count,a;
string s1,s2;
map<string,int> m; // 映射
map<string,int>::iterator it;
while (cin>>n && n!=0)
{
count =0;
for (i=0;i<1000;i++)
{
vex[i].clear();
degree[i] = 0;
}
for (i=0;i<n;i++)
{
// 难点:把名字和数组的i进行映射,不知道具体的队员个数
cin >>s1>>s2;
if (m.find(s1) == m.end())
m.insert(pair<string,int>(s1,count++));
if (m.count(s2)==0)
m.insert(pair<string,int>(s2,count++));
vex[m[s1]].push_back(m[s2]);
degree[m[s2]]++;
// 如果插入的字符串已经出现过了则不会执行该条语句的啦!名字和索引的映射成功!
// for (it = m.begin() ; it!=m.end();it++)
// cout << (*it).first << " : " << (*it).second << endl;
}
// !!!!!!
m.clear();
while (!q.empty())
q.pop();
for (i=0;i<m.size();i++)
if (degree[i] == 0)
q.push(i);
int cnt = 0;
while (!q.empty())
{
a = q.front();
q.pop();
cnt ++;
for (i=0;i<vex[a].size();i++)
{
if ((--degree[vex[a][i]])==0)
q.push(vex[a][i]);
}
}
if (cnt == 1)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0 ;
}
正确答案:
#include <stdio.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
#include <map>
using namespace std;
map<string,int> name;
vector<int> edge[10000];
queue<int> Q;
int indegree[10000];
int main(){
int n,i,j;
while(~scanf("%d",&n)&&n){
for(i=0;i<10000;i++) { edge[i].clear();indegree[i]=0;}
while(!Q.empty()) Q.pop();
i=0;
name.clear();
for(j=0;j<n;j++){
string a,b;
cin>>a>>b;
if(name.find(a)==name.end()) { name[a]=i++;}
if(name.find(b)==name.end()) { name[b]=i++;}
edge[name[a]].push_back(name[b]);
indegree[name[b]]++;
}
int num=i;
int cnt=0;
for(i=0;i<num;i++)
if(indegree[i]==0) cnt++;
/*
int num=i;
for(i=0;i<n;i++){
if(indegree[i]==0) Q.push(i);
}
int cnt=0;
while(!Q.empty()){
int nowp=Q.front();
Q.pop();
cnt++;
for(i=0;i<edge[nowp].size();i++){
indegree[edge[nowp][i]]--;
if(indegree[edge[nowp][i]]==0) Q.push(edge[nowp][i]);
}
}
*/
if(cnt==1) puts("Yes");
else puts("No");
}
return 0;
}
最短路径问题
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <map>
#include <string>
#define INFINITY 65535
using namespace std;
struct edge
{
int nextnode;
int d;//距离
int p;//花费
};
vector<edge> vex[1001]; // 邻接链表,vex[i]为一个向量,记录与vex[i]邻接的所有边的信息(邻接点以及权值)
bool S[1001]; // s集合为已经找到最短路径的顶点集合
int dis[1001]; // 距离向量
int cost[1001];//花费向量
int main()
{
int n,m,d,p,s,t,i,j,k,min,a,b;
edge e;
while (cin >> n >> m && n!=0 && m!=0)
{
// 1. 初始化邻接链表、距离向量和集合S,现在未知初始顶点和结束顶点是什么
for (i=1;i<=n;i++)
{
vex[i].clear();
S[i] = false;
dis[i] = INFINITY; // -1代表不可达
cost[i] = 0;
}
// 2.输入数据
for (i=0;i<m;i++)
{
cin >> a>>b>>d>>p;
e.d = d;e.p = p;
e.nextnode = b;
vex[a].push_back(e);
e.nextnode = a;
vex[b].push_back(e);
// 无向图,加入两条边
}
cin >> s >>t ;
// 确定初始顶点后,初始化集合S和dis
S[s] = true;dis[s] = 0; // 初始顶点自己到自己为0
for (i=0;i<vex[s].size();i++)
{
int num = vex[s][i].nextnode;
dis[num] = vex[s][i].d;
cost[num] = vex[s][i].p;
}
// 循环找到最短路径
for (i=2;i<=n;i++)
{
// 找到当前最短路径和顶点
min = INFINITY;
for (j=1;j<=n;j++)
{
if (!S[j] && min > dis[j] )
{
k = j;
min = dis[j];
}
}
S[k] = true;
for (j = 0;j<vex[k].size();j++)
{
int node = vex[k][j].nextnode;
if (S[node])
continue;
if (min+vex[k][j].d<dis[node] || min+vex[k][j].d == dis[node] && cost[node] > cost[k]+vex[k][j].p)
{
dis[node] = min + vex[k][j].d;
cost[node] = cost[k]+vex[k][j].p;
}
}
}
cout << dis[t]<<" "<< cost[t]<< endl;
}
return 0 ;
}