前言
算法竞赛进阶指南图论习题。慢慢刷。
Sightseeing
这个题求最短路以及比最短路大1的路的条数。关键是次短路如何构成。分析可以发现一个点的次短路一定为相邻点次短路或者最短路构成。所以dijkstra维护最短路和次短路即可。
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int INF=0x3f3f3f3f; const int N=1e3+10; const int M=1e4+10; int head[N],ver[M],edge[M],nex[M],tot=1; inline void add(int x,int y,int z) { ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot; } struct Node { bool operator<(const Node &t)const {return d>t.d;} Node(int x,int d):x(x),d(d){} int x,d; }; //dis1最短路dis2次短路cnt1最短路数量cnt2次短路数量 int dis1[N],dis2[N],cnt1[N],cnt2[N]; int dijkstra(int s,int t){ dis1[s]=0; cnt1[s]=1; priority_queue<Node> q; q.push(Node(s,0)); while(!q.empty()){ int x=q.top().x,d=q.top().d;q.pop(); if(d>dis2[x])continue; for(int i=head[x];i;i=nex[i]){ int y=ver[i],len=d+edge[i]; if(dis1[y]>len){ dis2[y]=dis1[y];cnt2[y]=cnt1[y]; dis1[y]=len;cnt1[y]=cnt1[x]; q.push(Node(y,len)); }else if(dis1[y]==len) cnt1[y]+=cnt1[x]; else if(dis2[y]>len){ dis2[y]=len; cnt2[y]=dis1[x]==d?cnt1[x]:cnt2[x]; q.push(Node(y,len)); }else if(dis2[y]==len) cnt2[y]+=dis1[x]==d?cnt1[x]:cnt2[x]; } } return cnt1[t]+(dis1[t]+1==dis2[t]?cnt2[t]:0); } void init(int n){ for(int i=1;i<=n;++i){ head[i]=0; dis1[i]=dis2[i]=INF; cnt1[i]=cnt2[i]=0; } tot=1; } int main(){ int T,n,m,s,t,x,y,w; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); init(n); for(int i=0;i<m;++i){ scanf("%d%d%d",&x,&y,&w); add(x,y,w); } scanf("%d%d",&s,&t); printf("%d ",dijkstra(s,t)); } return 0; }
升降梯上
把电梯的层数和控制槽的位置作为状态,把各状态提取出再进行连边,跑最短路。
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; const int N=1000*20+10; const int M=N*20+10; int head[N],ver[M],edge[M],nex[M],tot=1; inline void add(int x,int y,int z) { ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot; } struct Node { bool operator<(const Node &t)const{return d>t.d;} Node(int x,int d):x(x),d(d) {} int x,d; }; int dis[N],vis[N]; void dijkstra(int s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[s]=0; priority_queue<Node> pq; pq.push(Node(s,0)); while(!pq.empty()) { int x=pq.top().x; pq.pop(); if(vis[x])continue; vis[x]=1; for(int i=head[x]; i; i=nex[i]) { int y=ver[i]; if(dis[y]>dis[x]+edge[i]) { dis[y]=dis[x]+edge[i]; pq.push(Node(y,dis[y])); } } } } int n,m,c[30]; inline int encode(int x,int y){return (x-1)*m+y+1;} void build(){ for(int i=1;i<=n;++i) for(int j=0;j<m;++j) for(int k=0;k<m;++k) if(i+c[k]>=1&&i+c[k]<=n) add(encode(i,j),encode(i+c[k],k),abs(c[k]*2)+abs(j-k)); } int main(){ scanf("%d%d",&n,&m); int s,ans=INF; for(int i=0;i<m;++i){scanf("%d",c+i);if(c[i]==0)s=i;} build(); dijkstra(encode(1,s)); for(int i=0;i<m;++i) ans=min(ans,dis[encode(n,i)]); printf("%d",ans==INF?-1:ans); return 0; }
GF和猫咪的玩具
简单题,floyd
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; int mp[110][110]; int main(){ int n,m,x,y; memset(mp,INF,sizeof(mp)); scanf("%d%d",&n,&m); for(int i=0;i<m;++i){ scanf("%d%d",&x,&y); mp[x][y]=1; mp[y][x]=1; } for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); int ans=0; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(mp[i][j]!=INF)ans=max(ans,mp[i][j]); printf("%d",ans); return 0; }
社交网络
用floyd计算一下两点间最短路条数以及过一个点的最短路条数。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int mp[105][105]; ll cnt[105][105]; int main(){ int n,m,x,y,c; scanf("%d%d",&n,&m); memset(mp,0x3f,sizeof(mp)); for(int i=0;i<m;++i){ scanf("%d%d%d",&x,&y,&c); mp[x][y]=mp[y][x]=min(mp[x][y],c); cnt[x][y]=cnt[y][x]=1; } for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(mp[i][j]>mp[i][k]+mp[k][j]){ mp[i][j]=mp[i][k]+mp[k][j]; cnt[i][j]=cnt[i][k]*cnt[k][j]; } else if(mp[i][j]==mp[i][k]+mp[k][j]) cnt[i][j]+=cnt[i][k]*cnt[k][j]; double ans; for(int k=1;k<=n;++k){ ans=0; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(i!=k&&j!=k&&i!=j) ans+=(double)(mp[i][k]+mp[k][j]==mp[i][j]?cnt[i][k]*cnt[k][j]:0)/cnt[i][j]; printf("%.3f ",ans); } return 0; }
Arctic Network
kruskal求最小生成树,当联通块的数量等于卫星数量时结束,答案即最后加入的边长度。
#include<bits/stdc++.h> using namespace std; const int N=600; struct Edge{ Edge(int x,int y,int len2):x(x),y(y),len2(len2){} bool operator<(const Edge &t){return len2<t.len2;} int x,y,len2; }; int fa[N],x[N],y[N]; int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);} int dis2(int i,int j){return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);} int main(){ int t,s,p; scanf("%d",&t); while(t--){ scanf("%d%d",&s,&p); for(int i=1;i<=p;++i){ fa[i]=i; scanf("%d%d",x+i,y+i); } vector<Edge> e; for(int i=1;i<=p;++i) for(int j=1;j<=p;++j) if(i!=j)e.push_back(Edge(i,j,dis2(i,j))); sort(e.begin(),e.end()); double ans; for(Edge &t:e){ int u=get(t.x),v=get(t.y); if(u==v)continue; fa[u]=v; if(--p==s){ ans=sqrt(t.len2); break; } } printf("%.2f ",ans); } return 0; }
[SDOI2013]直径
自己想的智障方法:首先求了一下直径的条数,设为n,然后对每一条边计算经过它的直径条数是否为n。
查了题解的方法:
- 一个点的最长路的另一端一定为直径的端点
- 任意两条直径必定相交
那么答案一定为直径上的一段,随便求一条直径,从两边往中间缩就行了。
自己的方法的代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10; struct Edge{ Edge(int to,ll len):to(to),len(len){} int to; ll len; }; vector<Edge> G[N]; ll L[N],cnt[N],d,ans,cntd; typedef pair<ll,ll> P; void dfs(int x,int fa){ L[x]=0;cnt[x]=1; vector<P> v; for(Edge &t:G[x]){ int y=t.to;ll len=t.len; if(y==fa)continue; dfs(y,x); ll d=L[y]+len; v.push_back(P(d,cnt[y])); if(L[x]>d)continue; if(L[x]==d)cnt[x]+=cnt[y]; else{ L[x]=d;cnt[x]=cnt[y]; } } if(v.size()==0)return; sort(v.begin(),v.end(),greater<P>()); if(v.size()==1){ if(v[0].first>d){ d=v[0].first; cntd=v[0].second; } else if(v[0].first==d) cntd+=v[0].second; } else{ if(v[0].first!=v[1].first){ ll sum=v[1].second; for(int i=2;i<v.size();++i){ if(v[i].first!=v[i-1].first)break; sum+=v[i].second; } if(v[0].first+v[1].first>d){ d=v[0].first+v[1].first; cntd=v[0].second*sum; }else if(v[0].first+v[1].first==d) cntd+=v[0].second*sum; } else if(v[0].first+v[1].first>=d){ int i=0;ll sum=v[0].second; while(i+1<v.size()&&v[i+1].first==v[0].first) sum+=v[++i].second; ll s=(sum*(sum-1))/2; while(i>=0){ s-=(v[i].second*(v[i].second-1))/2; --i; } if(v[0].first+v[1].first>d){ d=v[0].first+v[1].first; cntd=s; } else cntd+=s; } } } void dfs2(int x,int fa,ll dd,ll c){ map<ll,ll> mp; mp[dd]=c; for(Edge &t:G[x]){ if(t.to==fa)continue; mp[t.len+L[t.to]]+=cnt[t.to]; } for(Edge &t:G[x]){ if(t.to==fa)continue; int y=t.to; ll C; ll tmp=d-t.len-L[y]; if(tmp==0)C=cnt[y]; else if(tmp==t.len+L[y])C=(mp[tmp]-cnt[y])*cnt[y]; else C=mp[tmp]*cnt[y]; if(C==cntd)++ans; } mp.clear(); mp[dd]=c; for(Edge &t:G[x]){ if(t.to==fa)continue; mp[t.len+L[t.to]]+=cnt[t.to]; } for(Edge &t:G[x]){ if(t.to==fa)continue; int y=t.to; ll tmp=t.len+L[y]; auto it=prev(mp.end()); if(it->first!=tmp) dfs2(y,x,it->first+t.len,it->second); else{ if(it->second-cnt[y]!=0) dfs2(y,x,it->first+t.len,it->second-cnt[y]); else{ it=prev(it); dfs2(y,x,it->first+t.len,it->second); } } } } int main(){ int n,x,y; ll len; scanf("%d",&n); for(int i=1;i<n;++i){ scanf("%d%d%lld",&x,&y,&len); G[x].push_back(Edge(y,len)); G[y].push_back(Edge(x,len)); } dfs(1,0); dfs2(1,0,0,1); printf("%lld %lld",d,ans); return 0; }
[NOI2003]逃学的小孩
最长的路径一定是C->A->B,A->B为直径。先求出一条直径,在从直径上每一个点往外跑到最远,如此枚举。设最长路径为C->A->B,如果这条路径不与直径交,设直径为D->E。那么C->A->D->E显然大于C->A->B。与假设矛盾。如果C->A->B与直径相交,那么把A->B换成直径一定不比原答案差。所以A->B一定为直径。如果树有多条直径,它们一定两两相交,如果考虑走的都是直径,那么似乎分叉点越接近当前直径中点会更优。
#include<bits/stdc++.h> typedef long long ll; using namespace std; const int N=2e5+10; const int M=2e5+10; int head[N],ver[2*M],edge[2*M],nex[2*M],tot=1; inline void add(int x,int y,int z) { ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot; } ll d; int pre[N],u,v,mid,vis[N]; ll dfs(int x,int fa){ ll mm=0,m=0; int xx=0,yy=0; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(fa==y)continue; ll dis=dfs(y,x)+edge[i]; if(dis>mm){ m=mm;yy=xx; mm=dis;xx=y; }else if(dis>m){ m=dis;yy=y; } } if(m+mm>d){ d=m+mm;u=xx;v=yy;mid=x; } pre[x]=xx; return mm; } ll dfs2(int x){ vis[x]=1; ll mm=0; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(vis[y])continue; mm=max(mm,dfs2(y)+edge[i]); } return mm; } vector<int> path; ll sum[N]; int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=0;i<m;++i){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } dfs(1,0); vis[mid]=2; while(u){ path.push_back(u); vis[u]=2,u=pre[u]; } reverse(path.begin(),path.end()); path.push_back(mid); while(v){ path.push_back(v); vis[v]=2,v=pre[v]; } for(int i=1;i<path.size();++i){ int x=path[i]; for(int j=head[x];j;j=nex[j]) if(ver[j]==path[i-1]){ sum[i]=sum[i-1]+edge[j]; break; } } ll ans=0; for(int i=0;i<path.size();++i){ int x=path[i]; ll c=dfs2(x); ll a=sum[i]; ll b=sum[path.size()-1]-sum[i]; ans=max(ans,a+b+c+min(a,b)); } printf("%lld",ans); return 0; }
[AHOI2008]聚会
一定存在一种方案pos->a,pos->b,pos->c没有走重复的边,这种方案即为最佳方案。那么可以枚举两两节点的LCA,取最小值。
#include<bits/stdc++.h> using namespace std; const int N=5e5+10; const int M=5e5+10; int head[N],ver[2*M],nex[2*M],tot=1; inline void add(int x,int y) { ver[++tot]=y,nex[tot]=head[x],head[x]=tot; } int d[N],f[N][30],max_t,n; void bfs() { max_t=log(n)/log(2)+1; d[1]=1; queue<int> q; q.push(1); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x]; i; i=nex[i]) { int y=ver[i]; if(d[y])continue; d[y]=d[x]+1;f[y][0]=x; for(int i=1; i<=max_t; ++i) f[y][i]=f[f[y][i-1]][i-1]; q.push(y); } } } int lca(int x,int y) { if(d[x]<d[y])swap(x,y); if(d[x]>d[y]) for(int i=max_t; i>=0; --i) if(d[f[x][i]]>=d[y]) x=f[x][i]; if(x==y)return x; for(int i=max_t; i>=0; --i) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } inline int getdis(int x,int y){ return d[x]+d[y]-2*d[lca(x,y)]; } int main(){ int m; scanf("%d%d",&n,&m); for(int i=1;i<n;++i){ int x,y; scanf("%d%d",&x,&y); add(x,y);add(y,x); } bfs(); while(m--){ int pos,cost; int x,y,z; scanf("%d%d%d",&x,&y,&z); pos=lca(x,y); cost=d[x]+d[y]-2*d[pos]+getdis(z,pos); int t=lca(x,z); int dd=d[x]+d[z]-2*d[t]+getdis(y,t); if(dd<cost){ pos=t;cost=dd; } t=lca(y,z); dd=getdis(x,t)+d[y]+d[z]-2*d[t]; if(dd<cost){ pos=t;cost=dd; } printf("%d %d ",pos,cost); } return 0; }