赛前训练2 连通性问题

以下,斜体表示注意点,粗体表示技巧点。

A

spfa 最长路环具有特殊性质考虑缩点

容易发现环上的点可以通过跑很多次直到点权全部为 \(0\),于是缩点跑 spfa 最长路即可。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=8e4+5,M=2e5+5;
int n,m,s,tot,cnt;
int dis[N],dfn[N],low[N],to[N],siz[N],kw[M];
bool instk[N],vis[N];
stack<int> stk;
struct NODE{
	int v,w;
};
struct EDGE{
	int u,v,w;
}e[M];
vector<int> scc[N];
vector<NODE> G[N],T[N];

void tarjan(int cur){
	stk.push(cur);
	instk[cur]=1;
	dfn[cur]=low[cur]=++cnt;
	for(auto i:G[cur]){
		if(!dfn[i.v])
			tarjan(i.v),low[cur]=min(low[cur],low[i.v]);
		else if(instk[i.v])
			low[cur]=min(low[cur],dfn[i.v]);
	}
	if(dfn[cur]==low[cur]){
		tot++;
		while(stk.top()!=cur){
			instk[stk.top()]=0;
			scc[tot].push_back(stk.top());
			to[stk.top()]=tot;
			stk.pop();
		}
		instk[cur]=0;
		scc[tot].push_back(cur);
		to[cur]=tot;
		stk.pop();
	}
}
void bfs(){
	queue<int> q;
	q.push(to[s]);
	dis[to[s]]=siz[to[s]];
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		for(auto i:T[cur]){
			if(dis[i.v]<dis[cur]+i.w+siz[i.v]){
				dis[i.v]=dis[cur]+i.w+siz[i.v];
				q.push(i.v); 
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<=m;i++){
		double d;
		cin>>u>>v>>w>>d;
		G[u].push_back({v,w});
		e[i]={u,v,w};
		int k=d*10;
		while(w)
			kw[i]+=w,w=w*k/10;
	}
	cin>>s;
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i);
	for(int i=1;i<=m;i++){
		if(to[e[i].u]!=to[e[i].v])
			T[to[e[i].u]].push_back({to[e[i].v],e[i].w});
		else
			siz[to[e[i].u]]+=kw[i];
	}
	bfs();
	int ans=0;
	for(int i=1;i<=tot;i++)
		ans=max(ans,dis[i]);
	cout<<ans;
	return 0;
}

B

必经边考虑割边,割边考虑边双

我们发现,必经边是割边的子集。

考虑缩边双,这样缩成的树的边全都是割边。

既然要边最多,选直径即可。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=3e5+5;
int n,m,x,ans=-1e9,cnt,id;
int dis[N],dfn[N],low[N],to[N],dcc[N];
bool instk[N],bridge[N];
struct NODE{
	int v,i;
};
vector<NODE> G[N];
vector<int> T[N];
pair<int,int> e[N];

void DFS(int cur,int fa,int sum){
	if(sum>=ans){
		ans=sum;
		x=cur;
	}
	for(int i:T[cur]){
		if(i==fa)
			continue;
		DFS(i,cur,sum+1);
	}
}
void tarjan(int cur,int edg){
	dfn[cur]=low[cur]=++cnt;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(!dfn[G[cur][nxt].v]){
			tarjan(G[cur][nxt].v,G[cur][nxt].i);
			low[cur]=min(low[cur],low[G[cur][nxt].v]);
			if(low[G[cur][nxt].v]>dfn[cur])
				bridge[G[cur][nxt].i]=1;
		}
		else if(G[cur][nxt].i!=edg){
			low[cur]=min(low[cur],dfn[G[cur][nxt].v]);
		}
	}
}
void dfs(int cur){
	dcc[cur]=id;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(dcc[G[cur][nxt].v]||bridge[G[cur][nxt].i])
			continue;
		dfs(G[cur][nxt].v);
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].push_back({v,i});
		G[v].push_back({u,i});
		e[i]={u,v};
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i,0);
	for(int i=1;i<=n;i++)
		if(!dcc[i])
			++id,dfs(i);
	for(int i=1;i<=m;i++){
		if(bridge[i]){
			T[dcc[e[i].first]].push_back(dcc[e[i].second]);
			T[dcc[e[i].second]].push_back(dcc[e[i].first]);
		}
	}
	DFS(1,0,0);
	DFS(x,0,0);
	cout<<ans;
	return 0;
}

C

图论结合背包模型

我们可以考虑把 \(p\) 个点对放进很多强连通分量里边。

然后发现这是个 01 背包模型,有 \(n\) 个物品,第 \(i\) 个体积为 \(\frac{i(i-1)}{2}\),价值为 \(i\),现在要最小化总价值。

所以第一问的答案即为 \(dp_p\)

第二问就很简单了,总共 \(\frac{dp_p(dp_p-1)}{2}\) 对点,减掉 \(p\) 个双向对是单向对个数(一定是最多的,因为去除了不连通的情况)。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+5;
int p;
int dp[N],w[N];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>p;
	for(int i=1;i<=p;i++)
		w[i]=i*(i-1)/2;
	memset(dp,0x3f,sizeof dp);
	dp[0]=0,dp[1]=2;
	for(int i=2;i<=p;i++)
		for(int j=w[i];j<=p;j++)
			dp[j]=min(dp[j],dp[j-w[i]]+i);
	cout<<dp[p]<<' '<<(dp[p]*(dp[p]-1)/2)-p;
	return 0;
}

D

见题解。

posted @ 2025-09-21 21:49  _KidA  阅读(7)  评论(0)    收藏  举报