7-1 图的深度优先搜索I (100 分)
无向图 G 有 n 个顶点和 m 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。
在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间
输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.
输出格式:
第1到n行,每行两个整数di和fi,用空格分隔,表示第i个顶点的发现时间和完成时间1≤i≤n 。
第n+1行,1个整数 k ,表示图的深度优先搜索树(森林)的边数。
第n+2到n+k+1行,每行两个整数u和v,表示深度优先搜索树(森林)的一条边<u,v>,边的输出顺序按 v 结点编号从小到大。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
1 6
3 4
2 5
7 12
8 11
9 10
4
3 2
1 3
4 5
5 6
思路:这道题很显然dfs,但注意最后要排序所以我们直接用sort来排序即可。直接上代码。
#include<iostream>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
int ctim[50005], wtim[50005];
vector<int>qqq[50005];
bool is[50005];
struct en {
int st;
int to;
}enn[50005];
int zzz;
int tim;
void dfs(int st) {
is[st] = 1;
int k, ze;
ctim[st] = ++tim;//时间戳+1
vector<int>::iterator it, h0;
if (!qqq[st].empty())
it = qqq[st].begin();
else
it = qqq[st].end();
h0 = qqq[st].end();
while (it != h0) {
if (!is[*it]) {
enn[zzz++] = { st,*it };
dfs(*it);
}
++it;
}
wtim[st] = ++tim;//结束后时间戳+1
}
bool operator <(const en& p, const en& q) {
return p.to < q.to;
}
inline void add(int i, int j) {
qqq[i].push_back(j);qqq[j].push_back(i);
}
int main() {
int n, m, i, j, k;
scanf("%d%d", &n, &m);
for (i = 0;i < m;++i) {
scanf("%d%d", &j, &k);
add(j, k);
}
for (i = 1;i <=n;++i) {
if (!is[i]) {//用标记数组,没有被搜索,就对其进行dfs。
dfs(i);
}
}
for (i = 1;i <= n;++i) {
printf("%d %d\n", ctim[i], wtim[i]);
}
printf("%d", zzz);
sort(enn, enn + zzz);//最后排序
for (i = 0;i < zzz;++i) {
printf("\n%d %d", enn[i].st, enn[i].to);
}
}
7-2 圆 (100 分)
二维平面上有n 个圆。请统计:这些圆形成的不同的块的数目。
圆形成的块定义如下: (1)一个圆是一个块; (2)若两个块有公共部分(含相切),则这两个块形成一个新的块,否则还是两个不同的块。
输入格式:
第1行包括一个整数n,表示圆的数目,n<=8000。
第2到n+1行,每行3 个用空格隔开的数x,y,r。(x,y)是圆心坐标,r 是半径。所有的坐标及半径都是不大于30000 的非负整数。
输出格式:
1个整数,表示形成的块的数目。
输入样例:
在这里给出一组输入。例如:
2
0 0 1
1 0 2
输出样例:
在这里给出相应的输出。例如:
1
思路:我们将可以连在一起的看作是一个集合,对我们就利用并查集,然后用路径压缩即可。我们直接上代码。
#include<iostream>
#include<algorithm>
using namespace std;
struct enn{
int x, y;
long long r;
}aa[8005];
int a[8005];
int find(int i) { //寻找该集合的根,并且利用路径压缩。
if (a[i] == i)return i;
else
a[i] = find(a[i]);
return a[i];
}
void jia(int i, int j) {
int k = find(j);
a[k] = find(i);将i所在的集合与j所在的集合连接
}
void add(int n) {
int i;long long k,j;
for (i = 1;i < n;++i)
{
j = (aa[i].x - aa[n].x) * (aa[i].x - aa[n].x);
k = (aa[i].y - aa[n].y) * (aa[i].y - aa[n].y);
k = j + k;
if (k <= (aa[i].r + aa[n].r) * (aa[i].r + aa[n].r)) {//满足条件就将两个集合合并。
jia(i, n);
}
}
}
int main() {
int n, m,k,i,j;long long r;
for (i = 0;i < 8005;++i) {
a[i] = i;
}
scanf("%d", &n);
for (i = 1;i <= n;++i) {
scanf("%d%d%lld", &j, &k, &r);
aa[i].x = j;aa[i].y = k;aa[i].r = r;
add(i);
}
k = 0;
for (i = 1;i <=n;++i) {
if (a[i] == i)
++k;
}
printf("%d", k);return 0;
}
7-3 供电 (100 分)
要给N个地区供电。每个地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给N个地区都供上电的最小费用。
输入格式:
第1行,两个个整数 N 和 M , 用空格分隔,分别表示地区数和修线路的方案数,1≤N≤10000,0≤M≤50000。
第2行,包含N个用空格分隔的整数P[i],表示在第i个地区建一个供电站的代价,1 ≤P[i]≤ 100,000,1≤i≤N 。
接下来M行,每行3个整数a、b和c,用空格分隔,表示在地区a和b之间修一条线路的代价为c,1 ≤ c ≤ 100,000,1≤a,b≤N 。
输出格式:
一行,包含一个整数, 表示所求最小代价。
输入样例:
在这里给出一组输入。例如:
4 6
5 4 4 3
1 2 2
1 3 2
1 4 2
2 3 3
2 4 3
3 4 4
输出样例:
在这里给出相应的输出。例如:
9
思路:我们可以利用prim算法,及每次加入一个点,计入n个就可以算出最终的结果,但是需要注意的是我们第一次加入的需要进行将每个节点都看作是初始结点,即每一个都可以建立自己的发电站。然后一个一个加入即可。
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
struct enn {
int st;
int to;
long long w;
};
vector<enn>qq[50005];
bool operator <(const enn& p,const enn& q) {
return p.w > q.w;
}
priority_queue<enn>q;//这里利用优先队列进行选择。
bool is[50005];//标记数组判断是否已经被选入。
int main() {
int n, m, i, j, k;
long long w;
scanf("%d%d", &n, &m);
for (i = 1;i <= n;++i) {
scanf("%lld", &w);
q.push({ i,i,w });//将初始的每个节点都压入。
}
for (i = 0;i < m;++i) {
scanf("%d%d%lld", &j, &k, &w);
qq[j].push_back({ j,k,w });
qq[k].push_back({ k,j,w });
}
enn ss;
long long cunt=0;
for (i = 0;i < n;++i) {
do { ss = q.top();q.pop(); } while (is[ss.to]);
is[ss.to] = 1;//将被选择的做标记
cunt += ss.w;//记录总的费用
while (!qq[ss.to].empty()) {
if (!is[qq[ss.to].back().to])
q.push(qq[ss.to].back());
qq[ss.to].pop_back();
}
}
printf("%lld", cunt);
}
7-4 发红包 (100 分)
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,c1的红包钱数要比c2的多。每个员工的红包钱数至少要发888元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
输入格式:
第1行,两个整数n和m(n<=10000,m<=20000),用空格分隔,分别代表员工数和要求数。
接下来m行,每行两个整数c1和c2,用空格分隔,表示员工c1的红包钱数要比c2多,员工的编号1~n 。
输出格式:
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出-1.
输入样例:
在这里给出一组输入。例如:
2 1
1 2
输出样例:
在这里给出相应的输出。例如:
1777
思路:拓扑排序,将每个员工的顺序排序,但是也要分层。这样我们就用拓扑排序然后在进行记录每层的个数然后再将总的结果加上该层的个数*层数。但是需要注意如果最后仍然有入度不为0的说明存在矛盾输出-1.
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
int du[10005];//记录入度
int z;
int top0;
vector<int>p[10005];
int main() {
int n, m, i, j, k,ji;
scanf("%d%d", &n, &m);
for (i = 0;i < m;++i) {
scanf("%d%d", &j, &k);
p[j].push_back(k);
du[k]++;//入度+1
}
z = n * 888;
queue<int> q;
for(i=1;i<=n;++i)
if (!du[i]) {
q.push(i);
top0++;//记录该层的个数
}
int ceng = 0;//记录层数
while (top0) {
++ceng;ji = 0;
for (i = 0;i < top0;++i) {
k = q.front();q.pop();
while (!p[k].empty())
{
j = p[k].back();p[k].pop_back();
if (!(--du[j])) {
q.push(j);++ji;
}
}
}
top0 = ji;
z += top0 * ceng;
}
for (i = 1;i <= n;++i) {
if (du[i]) {//不符合标准的输出-1.
printf("-1");return 0;
}
}
printf("%d", z);
return 0;
}