AtCoder Beginner Contest 309 - D(最短路) E(搜索?)

题目传送门:abc 309
前面的简单题就不放了

D - Add One Edge

题意:
给你一个无向图图,分为两个连通块,一个顶点数为 n1(1~n1),一个顶点数为 n2(n1+1~n1+n2),图中共有 m 条边。如果现在在两个连通块之间连接一条边,那么顶点 1 与顶点 n1+n2 则相互可达,且对于此种连法,两顶点之间存在一条最短的可达路径。问你在所有的情况中,最短路径的最大值是多少?
思路:

法一:dijkstra

因为问的是最短距离的最大,所以我们首先需要每一个连通块内两顶点到所有点的最短距离,又需要最大的最短距离,所以两个连通块内的最大距离之和+1即为最终答案。
首先利用 dij 处理顶点 1 和顶点 n1+n2 的单源最短路,设顶点 1 所在的连通块与顶点 1 的最远距离为 ans1,顶点 n1+n2 所在的连通块与顶点 n1+n2 的最远距离为ans2,最终的答案即为\(1+ans_1+ans_2\)
注意一点,就是\(0\le n_1+n_2\le 3e5\),注意全局变量的空间大小!

const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;
int n1,n2,m,ans[2];
vector<int> e[maxm],dis(maxm,inf);
vector<int> vis(maxm,false);

void dij(int x,int p){
	priority_queue<pii,vector<pii>,greater<pii>> q;
	q.push({0,x});
	pii t;
	while(!q.empty()){
		t=q.top();
		q.pop();
		if(vis[t.second]) continue;
		dis[t.second]=t.first;
		ans[p]=max(ans[p],t.first);
		vis[t.second]=true;
		for(auto a:e[t.second]){
			if(!vis[a] && dis[a]>t.first+1){
				q.push({t.first+1,a});
			}
		}
	}
	return ;
}

void solve(){
	cin>>n1>>n2>>m;
	int a,b;
	for(int i=0;i<m;++i){
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	dij(1,0);
	dij(n1+n2,1);
	cout<<ans[0]+ans[1]+1<<'\n';
	return ;
}

法二:BFS+队列

其实bfs基本思想与dij相同,利用BFS+队列逐层遍历

const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;
int n1,n2,m,ans[2];
vector<int> e[maxm],dis(maxm,inf);
vector<bool> vis(maxm,false);

void bfs(int x,int p){
	dis[x]=0;
	queue<int> q;
	q.push(x);
	int t;
	while(!q.empty()){
		t=q.front();
		q.pop();
		vis[t]=true;
		for(auto a:e[t]){
			if(!vis[a] && dis[a]>dis[t]+1){
				dis[a]=dis[t]+1;
				ans[p]=max(ans[p],dis[t]+1);
				q.push(a);
			}
		}
	}
	return ;
}

void solve(){
	cin>>n1>>n2>>m;
	int a,b;
	for(int i=0;i<m;++i){
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	bfs(1,0);
	bfs(n1+n2,1);
	cout<<ans[0]+ans[1]+1<<'\n';
	return ;
}

E - Family and Insurance

题意:
给你一个家族的父子关系,再给你部分人购买的家族保险的所能保的代数,问你这个家族共有多少人能够被保险保到?
思路:
简单题,但是自己犯了两个错误。。。
首先,父子关系有个限制\(1\le p_i\le i-1\),这说明序号大的一定是某个序号小的的子代(在Constraints中没看到啊没看到!)
第二点,全局变量默认初始化为0,但是非全局变量默认值又系统决定,所以,在使用需定义初值的非全局化变量之前,记得初始化啊!!!不然就会WA
由上我们可以对所有人存一个到自己的保险还能管几代-dp,在输入保险时不断更新单独某个人的最大值。之后因为父子关系是从前往后的,相当于我们已经知道题目的拓扑序了,所以我们从第2个人开始更新自己的保险管辖范围,除了自己的保险还有将父辈的保险继承下来,dp[i]=max(dp[i],dp[p[i]]-1)。最终,统计有多少人有保险覆盖即可(初值可以赋-1方便判断)
所以,题目给的是一个有权值的有根树,问权值向下传递,能覆盖多少节点
下为代码:

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long

using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*

*/
const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;

void solve(){
	int n,m,ans=0;
	cin>>n>>m;
	vector<int> p(n+1,0);
	for(int i=2;i<=n;++i){
		cin>>p[i];
	}
	vector<int> dp(n+1,-1);
	for(int i=0;i<m;++i){
		int x,y;
		cin>>x>>y;
		dp[x]=max(dp[x],y);
	}
	for(int i=2;i<=n;++i){
		dp[i]=max(dp[i],dp[p[i]]-1);
	}
	for(int i=1;i<=n;++i){
		if(dp[i]>=0) ++ans;
	}
	cout<<ans<<'\n';
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
//	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

也可以和上题一样,利用bfs+队列求解问题。这么说的话,dfs也可以。
下为bfs代码:

void bfs(int x){
	queue<int> q;
	q.push(x);
	while(!q.empty()){
		int t=q.front();
		q.pop();
		if(dp[t]>=0) ++ans;
		for(auto a:e[t]){//e[t]中存了t的直接子代的编号
			dp[a]=max(dp[a],dp[t]-1);
			q.push(a);
		}
	}
	return ;
}
posted on 2023-07-14 15:32  Qiansui  阅读(48)  评论(0编辑  收藏  举报