赛前训练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
见题解。

浙公网安备 33010602011771号