T1:
题目:
无向图 G 有 n 个顶点和 m 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。
在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间
INPUT
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数
第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
OUTPUT
1 6
3 4
2 5
7 12
8 11
9 10
4
3 2
1 3
4 5
5 6
Memory Limit
10 MB
Time Limit
200 ms
Data range
1≤n≤50000, 1≤m≤100000.
Solution
直接dfs即可,由于其特殊性用vector存图相较于链式前向星更方便
赛时AC代码
#include <bits/stdc++.h>
using namespace std;
int n,m;
bool vis[50005];
int d[50005],f[50005],tim;
vector<int> to[50005];
struct Node{
int a,b;
}edge[500005];
int tot;
int read(){
int x=0,y=1;char ch=getchar();
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')y=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*y;
}
void dfs(int x){
d[x] = ++tim;
vis[x] = 1;
for(int i=0;i<to[x].size();i++){
int y = to[x][i];
if(vis[y])continue;
++tot;
edge[tot].a = x;
edge[tot].b = y;
dfs(y);
}
f[x] = ++tim;
}
bool cmp(Node x,Node y){
return x.b < y.b;
}
int main(){
n = read(),m = read();
for(int i=1;i<=m;i++){
int u = read(),v = read();
to[u].push_back(v);
to[v].push_back(u);
}
for(int i=1;i<=n;i++)
if(!vis[i])dfs(i);
for(int i=1;i<=n;i++)
printf("%d %d\n",d[i],f[i]);
printf("%d\n",tot);
sort(edge+1,edge+1+tot,cmp);
for(int i=1;i<=tot;i++){
printf("%d %d",edge[i].a,edge[i].b);
if(i != tot)printf("\n");
}
return 0;
}
T2:
题目:
二维平面上有n 个圆。请统计:这些圆形成的不同的块的数目。
圆形成的块定义如下: (1)一个圆是一个块; (2)若两个块有公共部分(含相切),则这两个块形成一个新的块,否则还是两个不同的块。
INPUT
第1行包括一个整数n,表示圆的数目
第2到n+1行,每行3 个用空格隔开的数x,y,r。(x,y)是圆心坐标,r 是半径。
OUTPUT
1个整数,表示形成的块的数目。
Sample
INPUT
2
0 0 1
1 0 2
OUTPUT
1
Memory Limit
5 MB
Time Limit
500 ms
Data range
n<=8000。
所有的坐标及半径都是不大于30000 的非负整数。
Solution
本题与[NOIP2017 提高组] 奶酪类似,并查集或dfs都可,本人选择并查集
赛时AC代码
#include <bits/stdc++.h>
using namespace std;
int read(){
int x=0,y=1;char ch=getchar();
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')y=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*y;
}
int fa[10005],n;
void load(){
for(int i=1;i<=n;i++)fa[i]=i;
}
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void hebin(int x,int y){
fa[find(x)]=find(y);
}
struct Node{
double x,y,r;
}a[10005];
bool jud(int p,int q){
double dis = sqrt((a[p].x - a[q].x) * (a[p].x - a[q].x) + (a[p].y - a[q].y) * (a[p].y - a[q].y));
if(dis <= a[p].r + a[q].r)return 1;
return 0;
}
int main(){
n = read();
int ans = n;
for(int i=1;i<=n;i++){
scanf("%lf%lf%lf",&a[i].x,&a[i].y,&a[i].r);
fa[i] = i;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(find(i) != find(j)){
if(jud(i,j)){fa[find(i)] = find(j);ans--;}
}
printf("%d",ans);
return 0;
}
T3:
题目:
要给N个地区供电。每个地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给N个地区都供上电的最小费用。
INPUT
第1行,两个个整数 N 和 M , 用空格分隔,分别表示地区数和修线路的方案数
第2行,包含N个用空格分隔的整数P[i],表示在第i个地区建一个供电站的代价
接下来M行,每行3个整数a、b和c,用空格分隔,表示在地区a和b之间修一条线路的代价为c
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
OUTPUT
9
Memory Limit
10 MB
Time Limit
500 ms
Data range
1≤N≤10000,0≤M≤50000。
1 ≤P[i]≤ 100,000,1≤i≤N 。
1 ≤ c ≤ 100,000,1≤a,b≤N 。
Solution
明显的贪心,以下给出证明
1.使某一点集连通的最小代价必然是最小生成树
2.使某一连通块内通电的最小代价一定是在该连通块内建立最小代价的发电站
在以上两条的基础上,我们可以基于克鲁斯卡尔算法的思想,当一条边连接两个不相交的连通块时,我们才会考虑连接
并且连接的代价是该边的代价ci,节省的代价为max(min(p[block(a[i])]),min(p[block(b[i])]))
当节省的代价大于付出的代价时,我们才会加上该边
赛时AC代码
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
#define int long long
int read(){
int x=0,y=1;char ch=getchar();
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')y=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*y;
}
int n,m;
int p[100005];
int fa[100005];
int min_cost[100005];
struct Node{
int u,v,w;
}edge[100005];
bool vis[100005];
int find(int x){
if(fa[x] == x)return x;
return fa[x] = find(fa[x]);
}
bool cmp(Node x,Node y){
return x.w < y.w;
}
void merge(int x,int y){
fa[find(x)] = find(y);
}
void kruskal(){
long long ans = 0;
for(int i=1;i<=m;i++)
if(find(edge[i].u) != find(edge[i].v)){
int large = max(min_cost[find(edge[i].u)],min_cost[find(edge[i].v)]);
int small = min(min_cost[find(edge[i].u)],min_cost[find(edge[i].v)]);
if(edge[i].w <= large){
merge(edge[i].u,edge[i].v);
ans += edge[i].w;
min_cost[find(edge[i].u)] = small;
}
}
for(int i=1;i<=n;i++)
if(!vis[find(i)]){
ans += min_cost[find(i)];
vis[find(i)] = 1;
}
printf("%lld",ans);
}
signed main(){
n = read(),m = read();
for(int i=1;i<=n;i++){
p[i] = read();
min_cost[i] = p[i];
fa[i] = i;
}
for(int i=1;i<=m;i++)
edge[i].u = read(),edge[i].v = read(),edge[i].w = read();
sort(edge+1,edge+1+m,cmp);
kruskal();
return 0;
}
T4:
题目:
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,c1的红包钱数要比c2的多。每个员工的红包钱数至少要发888元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
INPUT
第1行,两个整数n和m,用空格分隔,分别代表员工数和要求数。
接下来m行,每行两个整数c1和c2,用空格分隔,表示员工c1的红包钱数要比c2多,员工的编号1~n 。
OUTPUT
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出-1.
Sample
INPUT
2 1
1 2
OUTPUT
1777
Memory Limit
10 MB
Time Limit
50 ms
Data range
n<=10000,m<=20000
Solution
根据大小关系建图拓扑排序即可。有环就-1
赛时AC代码
#include <bits/stdc++.h>
using namespace std;
int read(){
int x=0,y=1;char ch=getchar();
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')y=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*y;
}
int n,m,ans;
int to[20005],nex[20005],head[20005],ce;
int in[20005];
int mx[20005];
void add(int u,int v){
to[++ce] = v,nex[ce] = head[u],head[u] = ce;
}
void topo(){
queue<pair<int,int> > q;
for(int i=1;i<=n;i++){
if(!in[i]){q.push(make_pair(i,888));ans += 888;}
}
while(!q.empty()){
pair<int,int> x = q.front();q.pop();
int u = x.first;
for(int i=head[u];i;i=nex[i]){
int v = to[i];
mx[v] = max(mx[v],x.second);
in[v]--;
if(!in[v]){q.push(make_pair(v,mx[v] + 1));ans += mx[v] + 1;}
}
}
for(int i=1;i<=n;i++)
if(in[i]){printf("-1");exit(0);}
printf("%d",ans);
}
int main(){
n = read(),m = read();
for(int i=1;i<=m;i++){
int u = read(),v = read();
add(u,v);
in[v]++;
}
topo();
return 0;
}