第九周的题

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;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值