赞
踩
题意:建立一些站点,如果有一个检查站在i路口,保护j的条件是:i==j或者警察巡逻车可以从i走到j,并且能回到i。求最小花费和用最少点的方案数
显然是强连通分量
在跑tarjan的过程中记录每个强连通分量中cost的最小值,在输出时遍历累乘即可
要开longlong,wa了两发
#include<bits/stdc++.h> #define int long long using namespace std; constexpr int maxn=3e5+5; constexpr int mod=1e9+7; int n,m; int cost[maxn],dfn[maxn],low[maxn],z[maxn],tot,k,vis[maxn]; int t,scc[maxn],cnt[maxn],top,res[maxn]; vector<int>e[maxn]; vector<int>jl[maxn]; void tarjan(int u){ dfn[u]=low[u]=++tot; z[++top]=u;//入栈 vis[u]=1; for(auto v:e[u]){ if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v]){ low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]){ t++;//连通分量的标号 do{ scc[z[top]]=t; //属于这个连通分量 cnt[t]++; //记录这个环中有多少个点 res[t]=min(res[t],cost[z[top]]); jl[t].push_back(z[top]); vis[z[top]]=0; top--; //出栈 }while(u!=z[top+1]); } } signed main(){ cin>>n; memset(res,0x3f,sizeof res); for(int i=1;i<=n;i++){ cin>>cost[i]; } cin>>m; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); } for(int i=1;i<=n;i++){ if(!dfn[i])tarjan(i); } int ans=0,w=1; for(int i=1;i<=t;i++){ ans+=res[i]; int ww=0; for(auto v:jl[i]){ if(cost[v]==res[i])ww++; } w=((w*ww)%mod); // printf("cnt = %d\n",cnt[i]); } printf("%lld %lld",ans,w); return 0; }
题意:给定一棵节点数为 n 的树 , 删一条边然后加上一条边 , 使得该树的重心唯一 。(删掉的边和加上的边可以是同一条)
重心最多只能有两个
如果只有一个重心,输出唯一重心上的任意一条边两次即可
如果有两个重心,把b重心的上任意一条非跟a重心相连的边连到a重心上即可
#include<bits/stdc++.h> using namespace std; vector<int>e[200005]; int n,m,t,maxn1,maxn2,zx1,zx2; int d[200005]; void dfs(int s,int f){ d[s]=1; int res=0; for(auto v:e[s]){ if(v==f)continue; dfs(v,s); d[s]+=d[v]; res=max(res,d[v]); } res=max(res,n-d[s]); if(res<maxn1){ maxn1=res; zx1=s; } if(res==maxn1&&s!=zx1){ maxn2=maxn1; zx2=s; } } int main(){ cin>>t; while(t--){ cin>>n; maxn1=0x3f3f3f3f,maxn2=0x3f3f3f3f+1; memset(d,0,sizeof d); for(int i=1;i<=n;i++){ e[i].clear(); } for(int i=1;i<=n-1;i++){ int x,y; cin>>x>>y; e[x].push_back(y); e[y].push_back(x); } dfs(1,0); int f=0; if(maxn1!=maxn2){ for(auto v:e[zx1]){ printf("%d %d\n",zx1,v); printf("%d %d\n",zx1,v); f=1; break; } if(f)continue; } else if(maxn1==maxn2){ for(auto v:e[zx2]){ if(v!=zx1){ printf("%d %d\n",zx2,v); printf("%d %d\n",v,zx1); f=1; break; } } } } return 0; }
n 种花,k 个客人,每个人喜欢两种编号不同的花。但是每种花在花店里只有一束。
客人按顺序进入花店,会买走所有她喜欢且仍在店铺里的花。如果一个客人买不到任何一束花,那么她就会十分沮丧导致变成肥宅。现在你可以自己安排这 n 个人的顺序,使得肥宅的数量最小。
并查集,
#include<bits/stdc++.h> using namespace std; vector<int>e; int n,m; const int maxn=2e5+5; int f[maxn],vis[maxn]; int find(int x){ if(f[x]==x)return x; else return f[x]=find(f[x]); } inline void hb(int x,int y){ int fx=find(x); int fy=find(y); f[fx]=fy; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ f[i]=i; } for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; hb(x,y); } int cnt=0; for(int i=1;i<=n;i++){ if(f[i]==i)cnt++; } cout<<m-n+cnt; }
题意:在一个有向图中,求类似于上图的菱形,即4个点 a , b , c , d a,b,c,d a,b,c,d 同时存在 ( a , b ) , ( a , d ) , ( b , c ) , ( d , c ) (a,b),(a,d),(b,c),(d,c) (a,b),(a,d),(b,c),(d,c)四条边组成的菱形的个数
思路:遍历求出每个点距离为2的路径数,再用组合数 n选2
可以表示成 a ( a − 1 ) 2 \dfrac {a(a-1)}2 2a(a−1)
#include<bits/stdc++.h> #define int long long using namespace std; constexpr int maxn=3005,inf=0x3f3f3f3f; constexpr int dx[]={1,-1,0,0}; constexpr int dy[]={0,0,1,-1}; int n,m,t,k; vector<int>e[maxn]; int cnt[maxn][maxn]; signed main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); } for(int i=1;i<=n;i++){ for(auto u:e[i]){ for(auto v:e[u]){ if(i==v)continue; cnt[i][v]++; } } } int ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ ans+=(cnt[i][j]-1)*cnt[i][j]/2; } } cout<<ans; return 0; }
题意:给你一张 n n n个点 m m m条边的无向连通图(没有边权)。保证图中没有重边和自环
你的任务是选择至多 ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor ⌊2n⌋个点使得对于任意一个没有被选择的点都和至少一个选中的点相邻(换言之,通过一条边连接)
保证答案存在,如果有多个答案,输出任意一个
你将要回答多个独立的询问
思路:二分图染色
题目保证有解并且图连通,不用判断无解情况
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; constexpr int dx[]={1,-1,0,0}; constexpr int dy[]={0,0,1,-1}; int n,m,t,k; vector<int>e[maxn]; int cnt,color[maxn]; inline void init(){ cnt=0; for(int i=1;i<=n;i++){ e[i].clear(); color[i]=-1; } } void dfs(int u){ color[u]=0; for(auto v:e[u]){ if(color[v]!=-1)continue; dfs(v); if(!color[v])color[u]=1; } } signed main(){ cin>>t; while(t--){ cin>>n>>m; init(); int mm=0,nb=0; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } dfs(1); for(int i=1;i<=n;i++){ cnt+=color[i]; } cout<<cnt<<endl; for(int i=1;i<=n;i++){ if(color[i]){ cout<<i<<" "; } } cout<<endl; } return 0; }
题意:给你一个无向图,把所有边标记方向,并使整张图中没有距离 >=2 的路径,问你是否存在并输出方案。
第一行输出“YES”或“NO”,若存在,第二行按照读入顺序输出连边方向
思路:二分图染色
既然距离不能达到2,那就白点向黑点连,判断有解即可
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; constexpr int dx[]={1,-1,0,0}; constexpr int dy[]={0,0,1,-1}; int n,m,t,k; vector<int>e[maxn]; int cnt,color[maxn],vis[maxn]; int a[maxn],b[maxn]; void dfs(int u){ vis[u]=1; for(auto v:e[u]){ if(!vis[v]){ color[v]=color[u]^1; dfs(v); } else if(color[v]==color[u]){ cout<<"NO"; exit(0); } } } signed main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; a[i]=u; b[i]=v; e[u].push_back(v); e[v].push_back(u); } for(int i=1;i<=n;i++){ if(!vis[i])dfs(i); } cout<<"YES"<<endl; for(int i=1;i<=m;i++){ cout<<color[a[i]]; } return 0; }
题意:给你一个 n n n 个点 m m m 条边的无向图。
你需要给每个点一个点权,使得每条边连接的两个点点权奇偶不同。点权的值域为 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 。
请求出方案数对 998244353 998244353 998244353 取模的结果。
二分图染色,用上简单的组合,开longlong,处处求mod,一个地方不注意就炸哭了
#include<bits/stdc++.h> using namespace std; #define int long long constexpr int maxn=3e5+5,inf=0x3f3f3f3f,mod=998244353; int n,m,t; vector<int>e[maxn]; int ans1=0,ans2=0; int cnt,color[maxn],vis[maxn]; int flag=0; int Pow(int x, int k){ int ans=1;for(;k;k>>=1,x=(int)x*x%mod) if(k&1) ans=(int)ans*x%mod;return ans;} void dfs(int u,int tag){ if(flag)return; if(color[u])ans1++; else ans2++; vis[u]=tag; for(auto v:e[u]){ if(!vis[v]){ color[v]=color[u]^1; dfs(v,tag); } else if(color[v]==color[u]){ flag=1; return; } } } signed main(){ cin>>t; while(t--){ cin>>n>>m; for(int i=1;i<=n;i++){ e[i].clear(); vis[i]=0; color[i]=0; } flag=0; cnt=0; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } int ans=1; for(int i=1;i<=n;i++){ if(!vis[i]){ ++cnt; ans1=0,ans2=0; dfs(i,cnt); ans=(int)ans*(Pow(2,ans1)%mod+Pow(2,ans2)%mod)%mod; } } if(flag){ cout<<"0"<<endl; continue; } cout<<ans<<endl; } return 0; }
伯兰州立大学的医学部刚刚结束了招生活动。和以往一样,约80%的申请人都是女生并且她们中的大多数人将在未来4年(真希望如此)住在大学宿舍里。
宿舍楼里有 n n n个房间和一只老鼠!女孩们决定在一些房间里设置捕鼠器来除掉这只可怕的怪物。在 i i i号房间设置陷阱要花费 c i c_i ci伯兰币。房间编号从 1 1 1到 n n n。
要知道老鼠不是一直原地不动的,它不停地跑来跑去。如果 t t t秒时它在 i i i号房间,那么它将在 t + 1 t+1 t+1秒时跑到 a i a_i ai号房间,但这期间不会跑到别的任何房间里( i = a i i=a_i i=ai表示老鼠没有离开原来的房间)。时间从 0 0 0秒开始,一旦老鼠窜到了有捕鼠器的房间里,这只老鼠就会被抓住。
如果女孩们知道老鼠一开始在哪里不就很容易吗?不幸的是,情况不是这样,老鼠在第 0 0 0秒时可能会在从 1 1 1到 n n n的任何一个房间内。
那么女孩们最少要花多少钱设置捕鼠器,才能保证老鼠无论从哪个房间开始流窜最终都会被抓到?
思路:因为有n个点n条边,所以一定会存在环,每个点只有一条出边,所以这个环一定是在链的末尾,先用拓扑排序把环外的点消去,再在森林里找到各个环内cost最小的点,相加即可
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,cnt; int du[maxn],c[maxn],vis[maxn],ans[maxn]; vector<int>e[maxn]; void topo(){ queue<int>q; for(int i=1;i<=n;i++){ if(du[i]==0){ q.push(i); } } while(!q.empty()){ int u=q.front(); q.pop(); for(auto v:e[u]){ du[v]--; if(du[v]==0)q.push(v); } } } void dfs(int u,int tag){ if(vis[u]||!du[u])return; vis[u]=1; ans[tag]=min(ans[tag],c[u]); for(auto v:e[u]){ dfs(v,tag); } } int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>c[i]; } for(int i=1;i<=n;i++){ int v; cin>>v; du[v]++; e[i].push_back(v); } topo(); memset(ans,inf,sizeof ans); for(int i=1;i<=n;i++){ if(!vis[i]&&du[i])dfs(i,++cnt); } int res=0; for(int i=1;i<=cnt;i++){ res+=ans[i]; } cout<<res; }
给出 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le2\cdot 10^5 2≤n≤2⋅105 个节点, 2 ≤ m ≤ 2 ⋅ 1 0 5 2\le m\le2\cdot 10^5 2≤m≤2⋅105 条边的有向图,路径 p 1 , ⋯ , p k p_1,\cdots,p_k p1,⋯,pk,路径中没有重复元素,边 ( p i , p i + 1 ) (p_i,p_{i+1}) (pi,pi+1) 总是存在。
定义 s = p 1 , t = p k s=p_1,t=p_k s=p1,t=pk 。
有一个导航系统,若当前在节点 u u u,会构造一条从 u u u 到 t t t 的最短路径(这种路径可能不止一条,但导航系统只会选其中一条),设导航系统规划的下一个节点为 w w w,实际行走的下一个节点为 v v v 。
实际行走路线为 p p p,求可能的最少重构次数和最多重构次数。
思路:若当前点在最短路上,该点连边的点中,有其他点也为最短路上的点,可以选择重构,最大重构++
若当前点不在最短路上,因为必须重构,所以最大最小都++
建一个反图跑bfs,求出每一点到终点的最短路
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,k,cnt; int dis[maxn],a[maxn],vis[maxn],s,t; vector<int>e[maxn],r[maxn]; void bfs(){ dis[s]=0; queue<int>q; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); for(auto v:r[u]){ if(!dis[v]){ dis[v]=dis[u]+1; q.push(v); } } } } int main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); r[v].push_back(u); } cin>>k; for(int i=1;i<=k;i++){ cin>>a[i]; } s=a[k],t=a[1]; bfs(); int ans1=0,ans2=0; for(int i=1;i<k;i++){ if(dis[a[i]]==dis[a[i+1]]+1){ for(auto v:e[a[i]]){ if(v!=a[i+1]&&dis[v]==dis[a[i+1]]){ ans2++; // cout<<"!"<<endl; break; } } } else{ ans1++,ans2++; } } cout<<ans1-1<<" "<<ans2-1; }
题意:给出n个点的父亲,(若父亲为自己则为根节点)求出最少改掉多少个点的父亲可以使其成为一颗有根树
思路:用并查集判断树or环的个数,分两种情况:
若森林中有符合条件的树,选一个树根作为总根,将其他树or环的代表点连到根上即可输出,cnt-1
若森林中只有环,选一个环的代表点作为根,将其他的环的代表点连到根上即可,输出cnt(因为作为根的环把父节点改成了自己)
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,k,cnt,root; int f[maxn],a[maxn],vis[maxn]; vector<int>e[maxn]; int find(int x){ if(f[x]==x)return x; else return f[x]=find(f[x]); } inline void hb(int x,int y){ int fx=find(x); int fy=find(y); f[fx]=fy; } int main(){ cin>>n; for(int i=1;i<=n;i++){ f[i]=i; } for(int i=1;i<=n;i++){ cin>>a[i]; hb(i,a[i]); } for(int i=1;i<=n;i++){ if(f[i]==i&&a[i]==i){ root=i; } } if(root){ for(int i=1;i<=n;i++){ if(f[i]!=i)continue; cnt++; if(a[i]==i&&i!=root){ a[i]=root; } else a[i]=root; } cout<<cnt-1<<endl; } else{ for(int i=1;i<=n;i++){ if(f[i]!=i)continue; cnt++; if(!root)root=i; a[i]=root; } cout<<cnt<<endl; } for(int i=1;i<=n;i++){ printf("%d ",a[i]); } return 0; }
题意:有一颗以1为根,节点数为n的树,A在1号点,B在x号点,A,B交替行动,A的目标是行动次数尽量少,B的目标是行动次数尽量多,问在这种情况下,行动的次数.
思路:求出两个点到所有的点的最短时间,a先动,所以只要a到的时间比b要早就追不上,遍历所有点,更新最大时间即可
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5; int n,m,t,k,x,cnt,nb,mm; int dis1[maxn],dis2[maxn]; vector<int>e[maxn]; void dfs1(int u,int fa){ for(auto v:e[u]){ if(v==fa)continue; dis1[v]=dis1[u]+1; dfs1(v,u); } } void dfs2(int u,int fa){ for(auto v:e[u]){ if(v==fa)continue; dis2[v]=dis2[u]+1; dfs2(v,u); } } int main(){ cin>>n>>x; for(int i=1;i<=n-1;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } dfs1(x,x); dfs2(1,1); int ans=0; for(int i=2;i<=n;i++){ if(dis1[i]<dis2[i]){ ans=max(ans,dis2[i]*2); } } cout<<ans; }
题意:在宇宙里有 n n n 个星球,分别编号为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 。Jack现在在 1 1 1 号星球上,他要去 n n n 号星球。已知一些星球之间有双向的传送通道,Jack可以通过这些传送通道移动。每次传送需要一些时间,在不同的星球之间传送也可能需要不同时间。
当有其他人在使用这个星球的传送通道时,Jack无法离开这个星球。比如,如果有人在 t t t 时刻使用通道,那Jack只能在 t + 1 t+1 t+1 时刻离开(如果t+1时刻没有人在使用通道)。
现在,Jack想请你计算他最早可以在哪个时刻到达 n n n 号星球。Jack在0时刻出发。
思路:dij,到达每个点要停留一定的时间,用map保存每个点的哪个时间需要停留
#include<bits/stdc++.h> #define int long long using namespace std; typedef pair<int,int>pii; constexpr int maxn=1e5+5,inf=0x3f3f3f3f; struct edge{ int v,w; }; vector<edge>e[maxn]; map<pair<int,int>,int>wait; int n,m,k,t,cnt,mm,dis[maxn],vis[maxn]; bool judge(){ priority_queue<pii,vector<pii>,greater<pii>>q; memset(dis,inf,sizeof dis); memset(vis,0,sizeof vis); dis[1]=0; q.push({0,1}); while(!q.empty()){ pii x=q.top(); q.pop(); int u=x.second; int w=x.first; if(vis[u])continue; vis[u]=1; while(wait[{u,w}]){ w++; } for(auto ss:e[u]){ int v=ss.v; if(dis[v]>w+ss.w){ dis[v]=w+ss.w; q.push({dis[v],v}); } } } return true; } signed main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v,w; scanf("%lld%lld%lld",&u,&v,&w); e[u].push_back((edge){v,w}); e[v].push_back((edge){u,w}); } for(int i=1;i<=n;i++){ cin>>k; for(int j=1;j<=k;j++){ int res; scanf("%lld",&res); mm=max(mm,res); wait[{i,res}]=1; } } int ans=0; bool sb=judge(); ans=dis[n]; if(ans<inf)cout<<ans; else cout<<-1; return 0; }
给定一个 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1\leq n \leq 2\cdot10^5) n(1≤n≤2⋅105)个节点的树的 n − 1 n-1 n−1条边和这棵树的一个 B F S BFS BFS序 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,…,an,判断这个 B F S BFS BFS序是否是一个从节点 1 1 1开始的合法 B F S BFS BFS序,若合法则输出 Y e s Yes Yes,否则输出 N o No No
思路:把题目要求的bfs序列保存,对树的边进行排序,bfs序列中出现早的点排前面,再进行bfs,如果有出现点不符合该bfs序则直接跳出,否则合法
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5; int n,m,k,t,cnt; int a[maxn],b[maxn],vis[maxn]; vector<int>e[maxn]; inline void bfs(){ queue<int>q; q.push(1); vis[1]=1; while(!q.empty()){ int u=q.front(); q.pop(); if(u!=a[++cnt]){ printf("No\n"); exit(0); } for(auto v:e[u]){ if(!vis[v]){ vis[v]=1; q.push(v); } } } printf("Yes\n"); } int main(){ cin>>n; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } for(int i=1;i<=n;i++){ cin>>a[i]; b[a[i]]=i; } for(int i=1;i<=n;i++){ sort(e[i].begin(),e[i].end(),[](int x,int y){return b[x]<b[y];}); } bfs(); }
题意:有字符串 A A A, B B B,每次在 A A A中选取若干个相同的字母(设为 x x x),改成另一个字母(设为 y y y),需要满足 x < y x<y x<y,问将A改成B的最少操作
思路:一开始看错题目了,以为要连续的…结果是任意相同的子序列就可以,直接并查集即可,相当于不同字母间连了多少次边就需要改变多少次,a->b->c->e->k,并查集合并次数即为答案
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,t,k,cnt; int f[30]; int find(int x){ if(f[x]==x)return x; else return f[x]=find(f[x]); } void hb(int x,int y){ int fx=find(x); int fy=find(y); if(fx!=fy){ cnt++; f[fx]=fy; } } int main(){ cin>>t; while(t--){ cin>>n; string a1,a2; cin>>a1>>a2; for(int i=0;i<30;i++){ f[i]=i; } int flag=0; for(int i=0;i<n;i++){ if(a1[i]>a2[i]){ flag=1; break; } else if(a1[i]!=a2[i]){ hb(a1[i]-'a'+1,a2[i]-'a'+1); } } if(flag)cout<<"-1"<<endl; else cout<<cnt<<endl; cnt=0; } }
Greg有一个有边权的有向图,包含 n n n 个点。这个图的每两个点之间都有两个方向的边。Greg喜欢用他的图玩游戏,现在他发明了一种新游戏:
帮帮Greg,输出每一步之前要求的值。
思路:明示要用Floyd,可是又不可能每次删掉一个点再求一次,怎么办呢?这里要用逆向思维(之前kb专题有一题也是同样的思路),从后删除的边往前加边,这样就避免了每删除一次要重新计算整张图的麻烦,而且Floyd的过程跟松弛顺序是无关的,依次加入这些点,并标记为可行点,对整张图求最短路和,如果两点都为可行点则加上这两点的最短路
(题目提示了要开longlong)
#include<bits/stdc++.h> #define int long long using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,t,k,cnt,ans[maxn]; int dis[505][505],vis[505],now[505]; signed main(){ cin>>n; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ cin>>dis[i][j]; } } for(int i=1;i<=n;i++){ cin>>vis[i]; } for(int u=n;u>=1;u--){ int k=vis[u]; now[k]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==j)continue; dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); } } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==j)continue; if(now[i]&&now[j])ans[u]+=dis[i][j]; } } } for(int i=1;i<=n;i++){ cout<<ans[i]<<" "; } return 0; }
定义一张无向图是和谐的当且仅当:假设图中存在一条从 l l l 到 r r r 的路径,则图中也存在从 l l l 到 l + 1 , l + 2 , ⋯ , r − 1 l+1,l+2,\cdots,r-1 l+1,l+2,⋯,r−1 的路径。
给出一张无向图,求至少需要添加多少条边才能将其变为和谐的。
思路:求每个联通块最大编号,若后一个联通块的最小编号小于这个联通块的最大编号,则内部一定有相交部分,需要相连,ans++
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,k,t,cnt,mm; vector<int>e[maxn]; int vis[maxn],ye[maxn]; void dfs(int u){ vis[u]=1; mm=max(mm,u); for(auto v:e[u]){ if(!vis[v]){ dfs(v); } } } int main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } int ans=0; for(int i=1;i<=n;i++){ if(!vis[i]){ if(mm>i)ans++; dfs(i); } } cout<<ans; return 0; }
题目描述
Treeland国有n个城市,这n个城市连成了一颗树,有n-1条道路连接了所有城市。每条道路只能单向通行。现在政府需要决定选择哪个城市为首都。假如城市i成为了首都,那么为了使首都能到达任意一个城市,不得不将一些道路翻转方向,记翻转道路的条数为k。你的任务是找到所有满足k最小的首都。
乍一看想到暴力搜,然而2e5的次方肯定t,然后写了一个假的dfs,去看了题解发现是换根dp,去学了一下,换根dp的思路是找一个点作为根进行一遍dfs预处理出信息,然后再在根上进行二次dfs根据公式进行递推每个节点的信息,从而避免了对每个节点都做根遍历的巨大复杂度。
回到这题,题目给的是有向边,但是用有向边是没法遍历整棵树的,所以我们先要存成无向边,加上边权,1代表是正向,0代表是反向,在第一遍dfs的统计中,如果走到的是反向边,则需要翻转,ans++。
在第二遍dfs中,每个节点需要翻转的次数可以由其父亲推导而来,画个图
1做完第一遍dfs后,f[1]=2,开始第二遍dfs
点1到点4,是正向边,f[4]=f[1]+1=3
点4到点3,是反向边,f[3]=f[4]-1=2
点4到点2,是反向边,f[2]=f[4]-1=2
所以答案是
2
1 2 3
#include<bits/stdc++.h> using namespace std; constexpr int maxn=2e5+5,inf=0x3f3f3f3f; int n,m,k,t,cnt,f[maxn]; struct edge{ int v,w; }; vector<edge>e[maxn]; int dfs(int u,int fa){ int ans=0; for(auto x:e[u]){ int v=x.v; int w=x.w; if(v==fa)continue; if(!w)ans++; ans+=dfs(v,u); } return ans; } void dfs2(int u,int fa){ cnt=min(f[u],cnt); for(auto x:e[u]){ int v=x.v; int w=x.w; if(v==fa)continue; f[v]=f[u]+(w==1?1:-1); dfs2(v,u); } } int main(){ cin>>n; cnt=inf; for(int i=1;i<=n-1;i++){ int u,v; cin>>u>>v; e[u].push_back((edge){v,1}); e[v].push_back((edge){u,0}); } f[1]=dfs(1,0); dfs2(1,0); cout<<cnt<<endl; for(int i=1;i<=n;i++){ if(f[i]==cnt){ printf("%d ",i); } } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。