1本周考并查集、DFS、最短路径问题等
1、
题目:P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。
接下来 M 行,每行包含三个整数 Zi,Xi,Yi 。
当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当 Zi=2 时,输出 Xi 与 Yi 是否在同一集合内,是的输出 Y
;否则输出 N
。
输出格式
对于每一个Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
思路:
明显的并查集问题,需要判断两元素是否属于同一集合,需要合并两集合
代码:
# include <bits/stdc++.h>
using namespace std;
int n,m,z,x,y;
class UnionFind {
public:
UnionFind(int size) : parent(size), rank(size, 1) {
for (int i = 0; i < size; ++i) {
parent[i] = i; // 初始时,每个元素自成一个集合,代表元素是自己
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
// 按秩合并,将rank较小的集合合并到rank较大的集合中
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
private:
std::vector<int> parent;
std::vector<int> rank;
};
int main()
{
scanf("%d %d",&n,&m);
UnionFind uf(100010);
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&z,&x,&y);
if(z==1)
{
uf.unionSets(x, y);
}
else
{
if(uf.find(x) == uf.find(y)) printf("Y\n");
else printf("N\n");
}
}
return 0;
}
2、
题目:P8604 [蓝桥杯 2013 国 C] 危险系数
题目背景
抗日战争时期,冀中平原的地道战曾发挥重要作用。
题目描述
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数DF(x,y):
对于两个站点 x 和 y(x !=y), 如果能找到一个站点 z,当 z 被敌人破坏后,x 和 y 不连通,那么我们称 z 为关于 x,y 的关键点。相应的,对于任意一对站点 x 和 y,危险系数 DF(x,y) 就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
输入格式
输入数据第一行包含 2 个整数 n(2≤n≤1000),m(0≤m≤2000),分别代表站点数,通道数。
接下来 m 行,每行两个整数 u,v(1≤u,v≤n,u !=v) 代表一条通道。
最后 1 行,两个数 u,v,代表询问两点之间的危险系数DF(u,v)。
输出格式
一个整数,如果询问的两点不连通则输出 −1 。
思路:
用DFS搜索有多少路径,再看看每个点被访问了几次,如果路径数和点被访问数相等,就说明这个电是关键点
代码:
# include <bits/stdc++.h>
using namespace std;
# define l long long
l n,m,sum,x,y,u,v,dd=0;
int ti[1010]={0};
bool vis[1010]={0},a[1010][1010]={0};
void dfs(l now)
{
if(now==v) {
sum++;
for(int i=1;i<=n;i++)
{
if(vis[i]==1) ti[i]++;
}
}
else
{
for(int i=1;i<=n;i++)
if(a[now][i]==1&&vis[i]==0){//如果两点连通且下一步要走到的点未被走过,
vis[i]=1;//标记。
dfs(i);
vis[i]=0;//回溯一步。
}
}
}
int main()
{
scanf("%lld %lld",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld %lld",&x,&y);
a[x][y]=a[y][x]=1;
}
scanf("%lld %lld",&u,&v);
dfs(u);
if(sum>0)
{
for(int i=1;i<=n;i++)
{
if(ti[i]==sum) dd++;
}
printf("%d",dd-1);
}
else printf("-1");
}
3、
题目:P1330 封锁阳光大学
题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。
阳光大学的校园是一张由 n 个点构成的无向图,n 个点之间由 m 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。
询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。
输入格式
第一行两个正整数,表示节点数和边数。 接下来 m 行,每行两个整数 u,v,表示点 u 到点 v 之间有道路相连。
输出格式
仅一行如果河蟹无法封锁所有道路,则输出 Impossible
,否则输出一个整数,表示最少需要多少只河蟹。
思路:
每一条边所连接的点中至少有一个被选中,一条边连的两个点不能同时被选中,因此直接涂颜色,只需要找到每一个子连通图,对它进行黑白染色,然后取两种染色中的最小值,然后最后汇总,就可以了。
代码:
#include<bits/stdc++.h>
using namespace std;
struct Edge
{
int t;
int nexty;
}edge[200000];
int head[20000];
int cnt=0;//链式前向星
bool used[20000]={0};//是否遍历过
int col[20000]={0};//每一个点的染色
int sum[2];//黑白两种染色各自的点数
bool dfs(int node,int color)//染色(返回false即impossible)
{
if(used[node])//如果已被染过色
{
if(col[node]==color)return true;//如果仍是原来的颜色,即可行
return false;//非原来的颜色,即产生了冲突,不可行
}
used[node]=true;//记录
sum[col[node]=color]++;//这一种颜色的个数加1,且此点的颜色也记录下来
bool tf=true;//是否可行
for(int i=head[node];i!=0&&tf;i=edge[i].nexty)//遍历边
{
tf=tf&&dfs(edge[i].t,1-color);//如果当前节点已经被染色,那么直接返回tf的值;否则,将当前节点染上1-color的颜色,然后继续递归地对其邻边指向的节点进行染色操作,并将结果与tf进行逻辑与运算。
}
return tf;//返回是否完成染色
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int a,b;
while(m--)
{
scanf("%d%d",&a,&b);
cnt++;
edge[cnt].t=b;
edge[cnt].nexty=head[a];
head[a]=cnt; //将无向图输入数组
cnt++;
edge[cnt].t=a;
edge[cnt].nexty=head[b];
head[b]=cnt;
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(used[i])continue;
sum[0]=sum[1]=0;
if(!dfs(i,0))
{
printf("Impossible");
return 0;
}
ans+=min(sum[0],sum[1]);
}
printf("%d",ans);
return 0;
}
4、
题目:P3916 图的遍历
题目描述
给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点。
输入格式
第 1 行 2 个整数 N,M,表示点数和边数。
接下来 M 行,每行 2 个整数 Ui,Vi,表示边 (Ui,Vi)。点用 1,2,…,N 编号。
输出格式
一行 N 个整数 A(1),A(2),…,A(N)。
思路:
反向建边,DFS是一对多的,正向不行,直接从编号大的出发,看能到达哪几个点,直接用DFS
代码:
#include<bits/stdc++.h>
#define BIG 233333
using namespace std;
int n,m,x,y,tot;
int maxx[BIG],las[BIG],to[BIG],nxt[BIG];
inline void add(){
scanf("%d%d",&y,&x);
nxt[++tot]=las[x];
las[x]=tot;
to[tot]=y;
}
inline void dfs(int now,int st){
if(maxx[now])
return;
maxx[now]=st;
for(register int e=las[now];e;e=nxt[e])
if(!maxx[to[e]])
dfs(to[e],st);
return;
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;++i)
add();
for(register int i=n;i;--i)
dfs(i,i);
for(register int i=1;i<=n;++i)
printf("%d ",maxx[i]);
return 0;
}
5、
题目:P1119 灾后重建
题目背景
B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
题目描述
给出 B 地区的村庄数 N,村庄编号从 0 到N−1,和所有 M 条公路的长度,公路是双向的。并给出第 i 个村庄重建完成的时间 ti,你可以认为是同时开始重建并在第 ti 天重建完成,并且在当天即可通车。若ti 为 00 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q 个询问 (x,y,t),对于每个询问你要回答在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未重建完成,则需要输出 −1。
输入格式
第一行包含两个正整数 N,M,表示了村庄的数目与公路的数量。
第二行包含 N 个非负整数 t0,t1,⋯,tN−1,表示了每个村庄重建完成的时间,数据保证了 0≤t1≤⋯≤tN−1。
接下来 M 行,每行 3 个非负整数 i,j,w,w 为不超过 10000 的正整数,表示了有一条连接村庄 i 与村庄 j 的道路,长度为 w,保证 i=j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是 M+3 行包含一个正整数 Q,表示 Q 个询问。
接下来 Q 行,每行 3 个非负整数 x,y,t,询问在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少,数据保证了 t 是不下降的。
输出格式
共 Q 行,对每一个询问 (x,y,t) 输出对应的答案,即在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果在第 t 天无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未修复完成,则输出 −1。
代码:
#include<bits/stdc++.h>
#define N 205
using namespace std;
int n,m;
int a[N];
int f[N][N];//邻接矩阵存边
inline void updata(int k){
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(f[i][j]>f[i][k]+f[j][k])
f[i][j]=f[j][i]=f[i][k]+f[j][k];//用这个新的更新所有前面的
return;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)
scanf("%d",a+i);//依次输入每一个村庄建立完成时需要的时间
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
f[i][j]=1e9;//初始化为保证它不爆炸范围内的最大值
}
for(int i=0;i<n;i++)
f[i][i]=0;
int s1,s2,s3;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&s1,&s2,&s3);
f[s1][s2]=f[s2][s1]=s3;//初始化边长
}
int q;
cin>>q;
int now=0;
for(int i=1;i<=q;i++){//处理各询问
scanf("%d%d%d",&s1,&s2,&s3);
while(a[now]<=s3&&now<n){
updata(now);//依次更新点,使它可以被用来更新其他的点
now++;
}
if(a[s1]>s3||a[s2]>s3)cout<<-1<<endl;
else {
if(f[s1][s2]==1e9)cout<<-1<<endl;
else cout<<f[s1][s2]<<endl;
}
}
return 0;
}