【JLU数据结构荣誉课】第五次上机实验
->点我进原题
-> 7-1 图的深度优先搜索I
-> 7-2 圆
-> 7-3 供电
-> 7-4 发红包
7-1 图的深度优先搜索I (100 分)
代码长度限制 \(16 KB\)
时间限制 \(200 ms\)
内存限制 \(10 MB\)
Description
无向图 \(G\) 有 \(n\) 个顶点和 \(m\) 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。
在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间
Input
第 \(1\) 行,\(2\) 个整数 \(n\) 和 \(m\),用空格分隔,分别表示顶点数和边数, \(1≤n≤50000\), \(1≤m≤100000\).
第 \(2\) 到 \(m+1\) 行,每行两个整数 \(u\) 和 \(v\),用空格分隔,表示顶点 \(u\) 到顶点 \(v\) 有一条边,\(u\) 和 \(v\) 是顶点编号,\(1≤u,v≤n\).
Output
第 \(1\) 到 \(n\) 行,每行两个整数 \(di\) 和 \(fi\),用空格分隔,表示第 \(i\) 个顶点的发现时间和完成时间 \(1≤i≤n\)。
第 \(n+1\) 行,\(1\) 个整数 \(k\) ,表示图的深度优先搜索树(森林)的边数。
第 \(n+2\) 到 \(n+k+1\) 行,每行两个整数 \(u\) 和 \(v\),表示深度优先搜索树(森林)的一条边 \(<u,v>\) ,边的输出顺序按 \(v\) 结点编号从小到大。
Sample Input
6 5
1 3
1 2
2 3
4 5
5 6
Sample Output
1 6
3 4
2 5
7 12
8 11
9 10
4
3 2
1 3
4 5
5 6
思路
dfs裸题,很适合新手练习图的遍历。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int f = 0, x = 0;
rg char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while( isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f? -x: x;
}
struct edge{
int uu, vv;
}tr[100010];
int n, m, st[100010], ed[100010], cnt, t;
bool vis[100010];
vector<int> e[100010];
inline bool cmp(edge a, edge b){
return a.vv < b.vv;
}
inline void dfs(rg int u){
st[u] = ++t;
for(rg int i = 0; i < e[u].size(); ++i){
int v = e[u][i];
if(!vis[v]){
tr[++cnt].uu = u;
tr[cnt].vv = v;
vis[v] = 1;
dfs(v);
}
}
ed[u] = ++t;
}
signed main(){
n = read(), m = read();
for(rg int i = 1; i <= m; ++i){
int u = read(), v = read();
e[u].push_back(v);
e[v].push_back(u);
}
for(rg int i = 1; i <= n; ++i){
if(!vis[i]){
vis[i] = 1;
dfs(i);
}
}
for(rg int i = 1; i <= n; ++i){
printf("%d %d\n", st[i], ed[i]);
}
printf("%d\n", cnt);
sort(tr + 1, tr + cnt + 1, cmp);
for(rg int i = 1; i <= cnt; ++i){
printf("%d %d\n", tr[i].uu, tr[i].vv);
}
return 0;
}
7-2 圆 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(500 ms\)
内存限制 \(5 MB\)
Description
二维平面上有 \(n\) 个圆。请统计:这些圆形成的不同的块的数目。
圆形成的块定义如下: (1)一个圆是一个块; (2)若两个块有公共部分(含相切),则这两个块形成一个新的块,否则还是两个不同的块。
Input
第 \(1\) 行包括一个整数 \(n\),表示圆的数目,\(n<=8000\)。
第 \(2\) 到 \(n+1\) 行,每行 \(3\) 个用空格隔开的数 \(x\),\(y\),\(r\)。\((x,y)\) 是圆心坐标,\(r\) 是半径。所有的坐标及半径都是不大于 \(30000\) 的非负整数。
Output
1个整数,表示形成的块的数目。
Sample Input
2
0 0 1
1 0 2
Sample Output
1
思路
计算各圆是不是之间是不是相切或者相交,如果是,则用并查集合并,最后查询剩下几个联通块即可。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int f = 0, x = 0;
rg char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while( isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f? -x: x;
}
int n, f[8010], x[8010], y[8010], r[8010], cnt;
inline int find(int v){
return f[v] == v ? v : f[v] = find(f[v]);
}
inline bool merge(rg int x, rg int y){
int f1 = find(x);
int f2 = find(y);
if(f1 != f2){
f[f2] = f1;
return true;
}
return false;
}
inline float cal(int x1, int y1, int x2, int y2){
return sqrt(((float)x2 - (float)x1) * ((float)x2 - (float)x1) + ((float)y2 - (float)y1) * ((float)y2 - (float)y1));
}
signed main(){
n = read();
cnt = n;
for(rg int i = 1; i <= n; ++i) f[i] = i;
for(rg int i = 1; i <= n; ++i){
x[i] = read(), y[i] = read(), r[i] = read();
for(rg int j = 1; j < i; ++j){
if(cal(x[i], y[i], x[j], y[j]) <= (float)r[i] + (float)r[j]){
if(merge(i, j)){
cnt --;
}
}
}
}
printf("%d", cnt);
return 0;
}
7-3 供电 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(500 ms\)
内存限制 \(10 MB\)
Description
要给 \(N\) 个地区供电。每个地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给 \(N\) 个地区都供上电的最小费用。
Input
第 \(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\) 。
Output
一行,包含一个整数, 表示所求最小代价。
Sample Input
4 6
5 4 4 3
1 2 2
1 3 2
1 4 2
2 3 3
2 4 3
3 4 4
Sample Output
9
思路
要求最小费用,费用的产生是来自建造供电站(点权)和修一条路连接到其他有点的地区(边权),考虑建造最小生成树,遍历每条边,在便利的过程中,对于每条边的链接,将边的两个端点所对应的连通块的供电站最小值之和与两个连通块总的最小值加边权的值做对比,来判断合并与否,如果合并,就将边权加入答案,最后遍历每个连通块,将每个连通块的最小供电站加入答案。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int f = 0, x = 0;
rg char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while( isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f? -x: x;
}
int n, m, p[10010], f[10010], cnt, minn, pf[10010];
ll ans = 0;
bool vis[10010];
struct node{
int u, v, w;
}e[100010];
inline bool cmp(node a,node b){
return a.w < b.w;
}
inline int find(int v){
return f[v] == v ? v : f[v] = find(f[v]);
}
inline bool judge(int x,int y){
int t1 = find(x);
int t2 = find(y);
if(t1 != t2){
return true;
}
return false;
}
inline void merge(int x,int y){
int t1 = find(x);
int t2 = find(y);
f[t2] = t1;
}
signed main(){
n = read(), m = read();
int tmp = 0x7fffffff;
for(rg int i = 1; i <= n; ++i){
p[i] = read();
}
for(rg int i = 1; i <= m; ++i){
e[i].u = read(), e[i].v = read(), e[i].w = read();
}
sort(e+1,e+m+1,cmp);
for(rg int i = 1; i <= n; ++i) f[i] = i;
for(rg int i = 1; i <= n; ++i) pf[i] = p[i];
ans = 0;
for(int i = 1; i <= m; ++i){
if(judge(e[i].u, e[i].v)){
// cnt ++ ;
if(pf[find(e[i].u)] + pf[find(e[i].v)] > e[i].w + min(pf[find(e[i].u)], pf[find(e[i].v)])){
ans += e[i].w;
int minr = min(pf[find(e[i].u)], pf[find(e[i].v)]);
merge(e[i].u, e[i].v);
pf[find(e[i].u)] = minr;
pf[find(e[i].v)] = minr;
}
}
if(cnt == n - 1) break;
}
for(rg int i = 1; i <= n; ++i)
if(!vis[find(i)])
ans += pf[find(i)], vis[find(i)] = 1;
printf("%lld", ans);
return 0;
}
7-4 发红包 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(50 ms\)
内存限制 \(10 MB\)
Description
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,\(c1\) 的红包钱数要比 \(c2\) 的多。每个员工的红包钱数至少要发 \(888\) 元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
Input
第 \(1\) 行,两个整数 \(n\) 和 \(m\) (\(n<=10000,m<=20000\)),用空格分隔,分别代表员工数和要求数。
接下来 \(m\) 行,每行两个整数 \(c1\) 和 \(c2\),用空格分隔,表示员工 \(c1\) 的红包钱数要比 \(c2\) 多,员工的编号 \(1\)~\(n\) 。
Output
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出 \(-1\).
Sample Input
2 1
1 2
Sample Output
1777
思路
\(c1\) 要比 \(c2\) 多,我们可以以 \(c2\) 为始边、\(c1\) 为终边建图跑拓扑排序,记录一个 \(money\),在拓扑排序的过程中,终边等于始边的 \(money\) 加一,最后统计答案就是每个人的 \(money\) 再加上 \(888\) 的和。
代码
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<queue>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int f=0,x=0;
rg char ch=getchar();
while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
int n,m,head[20010],tot,a[20010],cnt,deg[20010],money[20010];
ll ans;
queue<int > q;
struct edge{
int to,nxt;
}e[20010];
inline void add(int u,int v){
e[++tot].to=v;
e[tot].nxt=head[u];
deg[v]++;
head[u]=tot;
}
inline void topsort(){
for(rg int i=1;i<=n;++i) if(!deg[i]) q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
cnt++;
for(rg int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
money[v] = money[u] + 1;
if(--deg[v]==0) q.push(v);
}
}
}
signed main(){
n=read(),m=read();
for(rg int i=1,u,v;i<=m;++i){
u=read(),v=read();
add(v,u);
}
topsort();
if(cnt != n){
printf("-1\n");
return 0;
}
for(rg int i = 1; i <= n; ++i) ans += money[i] + 888;
printf("%lld", ans);
return 0;
}