2017.4.3 机房测试 (并查集,最短路,拓扑排序,最小生成树)
2017-04-03 21:53
701 查看
jyb远在北京仍不忘为我们出题。
这次是喜闻乐见的图论专题。
【解题报告】
用链表什么的暴力枚举题意,最坏复杂度是O(m^2)的。
注意到每次都是移动一摞砖,其实问题就是集合的合并,只是这个集合是一个有序的,我们还需要知道每个元素在集合中的位置。我们可以利用并查集高效的来解决。我们引入一个dep数组,对于祖先节点(将每摞砖最上方的一块设为祖先,设最下面的为祖先也是可以的),我们记录该集合中一共有多少块砖,对于其他节点,我们记录它上方有多少块砖,这样我们可以很容易地计算出下方有多少块。这样在合并x和y(设他们祖先为fx,fy)时,我们将dep[fx]赋给dep[fy](因为移到了上方,fx有多少块fy上方就有多少块),然后dep[fx]要加上原本的dep[fy]。难点就是在合并的过程中,dep数组怎么维护。其实很简单,只需要在路径压缩时顺带维护即可,如果一个节点x的祖先fx不是祖先(即已被合并过),那么dep[x]加上dep[fx]即可(等于头上又多了一摞砖,所以要加上)。
时间复杂度O(α(m))
代码如下:
下面是自己的代码
【解题报告】
如果没有大雾天气,很显然就是求一个最短路就行了。但是由于大雾天气的存在,如果大雾天气出现在乐最短路的路径上,那么肯定不能按照最快的时间到达,如果这样的话很可能就失信与客户。所以我们就需要枚举最短路径上每条高速公路出现大雾天气的情况,再求最短路,可得一个最糟的情况,即得到在不失信于客户的前提下的最快时间。
注意到数据范围,每条高速公路的行驶时间最多相差10倍,spfa的时间复杂度的上界大概为O(kM),k = 10,而朴素Dijkstra求一次最短路为稳定O(n^2),总的最坏为O(n^3),所以我们采用spfa或者堆优化Dijkstra算法(jyb写的STL堆优被卡了两个点)求最短路。
(特别坑的是题里面是单向边)
代码如下:
暴力做法:Floyd求任意两点连通性。(给了30%的分)
我们知道,对于在同一SCC中的两点,它们是互相可达的,这也意味着一个SCC中的所有点和其他点的连通性都是一样的,显然我们可以将原图缩点简化问题。经过tarjan缩点后,得到了一个DAG,我们很容易想到如果一个图“分叉”,那么分叉上的点肯定是互不可达的,所以原图必须是“一条链”。为什么对链打引号呢?是因为这条链不用很严格,比如1->2 2->3 3->4 1->4 2->4这样的图也是满足的,且可能还有重边,所以只用入度出度去判断的方法容易出错,dfs去走一条链的办法也比较麻烦(我没想出有什么简单的办法)。我们利用拓扑排序,若一直都是一个入度为0的点(队中至多有一个点),那么就是满足题意的。
Ps:可以先写一个floyd来对拍(因为时间代价很低)
时间复杂度O(n+m)
代码如下:
下面是我自己的代码
【解题报告】
最朴素的做法:枚举航空公司,将本公司的航线加入,然后做一遍MST,时间复杂度是O(klogk+mk)的。
但我们注意到“在最小生成树中任意一条边都是连接两个集合边权最小的一条边”,这个是MST一个非常重要的结论,是Prim和kruscal算法中贪心的核心。利用这条性质我们可以知道,对于每个航空公司,我们只需要原图MST中得到的航线即可得到最优购买方案。所以更优的做法是:先跑一次MST,把得到的边记录下来,然后枚举航空公司,还是先讲本公司的边全部加进去,然后只用扫一边第一次MST中记录下来的边,用kruscal的方法去构造树即可。时间复杂度为O(klogk+nm)。
对于本题,50%的数据是可以O(mk)过的,最后30%的数据我特别构造了一下,必须枚举到排序后的最后几条边才能构造出一颗生成树,所以O(mk)应该是过不了的,此外的20%数据是随机数据,加了构造出树就跳出判断的代码可能会过(但是jyb写的mk的没过。。);另外,50%的数据需要long long
代码如下:
这次是喜闻乐见的图论专题。
【解题报告】
用链表什么的暴力枚举题意,最坏复杂度是O(m^2)的。
注意到每次都是移动一摞砖,其实问题就是集合的合并,只是这个集合是一个有序的,我们还需要知道每个元素在集合中的位置。我们可以利用并查集高效的来解决。我们引入一个dep数组,对于祖先节点(将每摞砖最上方的一块设为祖先,设最下面的为祖先也是可以的),我们记录该集合中一共有多少块砖,对于其他节点,我们记录它上方有多少块砖,这样我们可以很容易地计算出下方有多少块。这样在合并x和y(设他们祖先为fx,fy)时,我们将dep[fx]赋给dep[fy](因为移到了上方,fx有多少块fy上方就有多少块),然后dep[fx]要加上原本的dep[fy]。难点就是在合并的过程中,dep数组怎么维护。其实很简单,只需要在路径压缩时顺带维护即可,如果一个节点x的祖先fx不是祖先(即已被合并过),那么dep[x]加上dep[fx]即可(等于头上又多了一摞砖,所以要加上)。
时间复杂度O(α(m))
代码如下:
#include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #include<string> #include<iostream> #include<queue> #include<cstdio> using namespace std; int n,m; int x,y; char c; int father,t1,t2; int fa[3000010]; // int dep[3000010]; // int getfather(int x) { if(x == fa[x]) // return x; else { int f = fa[x]; //用来判断自己记录的祖先是否真的是祖先 fa[x] = getfather(fa[x]); //路径压缩 if(fa[f] != f) //如果不是,则需要更新上面的砖块数 dep[x] += dep[f]; return fa[x]; } } void Init() // { for(int i = 1; i <= n; i++) { fa[i] = i; dep[i] = 1; } } int main() { freopen("bricks.in","r",stdin); freopen("bricks.out","w",stdout); scanf("%d%d",&n,&m); Init(); while(m--) { scanf(" %s",&c); if(c == 'M') { scanf("%d%d",&x,&y); t1 = getfather(x); t2 = getfather(y); if(t1 != t2) { fa[t2] = t1; //合并 int ll = dep[t2]; //t2上面的砖块数等于t1那摞砖的总数 dep[t2] = dep[t1]; // dep[t1] += ll; //而t1的总数也需要加上t2的总数 } } else { scanf("%d",&x); //因为问的在下面的砖块,所以减去自己 if(x == fa[x]) //因为祖先和非祖先,dep数组的含义不 //同需要分开讨论,祖先减去自己即可 { printf("%d\n",dep[x] - 1); continue; } father = getfather(x); //非祖先的话,因为dep保存的是自己上面还有多少个,所以用总的减去上面的和自己,即可得到下面有多少个 printf("%d\n",dep[father] - dep[x] - 1); } } }
下面是自己的代码
//poj 1998 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define N 100000 int n,m; int fa[2*N],sum[2*N],dis[2*N]; int getfather(int x) { int t; if(fa[x]!=x) { t=fa[x]; fa[x]=getfather(fa[x]); dis[x]+=dis[t]; } return fa[x]; } void move(int x,int y) { x=getfather(x),y=getfather(y); if(x!=y) { fa[y]=x; dis[y]=sum[x]; sum[x]+=sum[y]; } } int main() { freopen("bricks.in","r",stdin); freopen("bricks.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) fa[i]=i,sum[i]=1,dis[i]=0; while(m--) { char s[10]; int x,y; scanf("%s",s); if(s[0]=='M') { scanf("%d%d",&x,&y); move(x,y); } else { scanf("%d",&x); printf("%d\n",sum[getfather(x)]-dis[x]-1); } } return 0; }
【解题报告】
如果没有大雾天气,很显然就是求一个最短路就行了。但是由于大雾天气的存在,如果大雾天气出现在乐最短路的路径上,那么肯定不能按照最快的时间到达,如果这样的话很可能就失信与客户。所以我们就需要枚举最短路径上每条高速公路出现大雾天气的情况,再求最短路,可得一个最糟的情况,即得到在不失信于客户的前提下的最快时间。
注意到数据范围,每条高速公路的行驶时间最多相差10倍,spfa的时间复杂度的上界大概为O(kM),k = 10,而朴素Dijkstra求一次最短路为稳定O(n^2),总的最坏为O(n^3),所以我们采用spfa或者堆优化Dijkstra算法(jyb写的STL堆优被卡了两个点)求最短路。
(特别坑的是题里面是单向边)
代码如下:
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <cstdlib> #include <algorithm> #include <iostream> using namespace std; const int maxn = 4010; const int maxm = 20010; struct edge { int u,v,next; double w; }e[maxm]; int h[maxn],num; double dis[maxn]; int pre[maxn]; bool vis[maxn]; int q[maxn],head,tail; int n,m; void build(int u,int v,double w) { num++; e[num].u = u; e[num].v = v; e[num].w = w; e[num].next = h[u]; h[u] = num; } void init() { for(int i = 2; i <= n; i++) dis[i] = 1000000.0; head = 0; tail = 1; q[0] = 1; vis[1] = true; } double spfa(bool first) { int v,u; init(); while(head != tail) { u = q[head]; for(int i = h[u]; i; i = e[i].next) { v = e[i].v; if(dis[v] > dis[u] + e[i].w) { dis[v] = dis[u] + e[i].w; if(first) pre[v] = i; if(!vis[v]) { vis[v] = true; q[tail] = v; tail = (tail + 1) % n; } } } vis[u] = false; head = (head + 1) % n; } return dis ; } int main() { freopen("drive.in","r",stdin); freopen("drive.out","w",stdout); scanf("%d%d",&n,&m); int u,v; double sp,len; for(int i = 1; i <= m; i++) { scanf("%d%d%lf%lf",&u,&v,&sp,&len); build(u,v,len/sp); } int now = n; double ans = spfa(true); while(now != 1) { e[pre[now]].w *= 4.0; ans = max(ans,spfa(false)); e[pre[now]].w /= 4.0; now = e[pre[now]].u; } printf("%.4lf\n",ans); return 0; }
暴力做法:Floyd求任意两点连通性。(给了30%的分)
我们知道,对于在同一SCC中的两点,它们是互相可达的,这也意味着一个SCC中的所有点和其他点的连通性都是一样的,显然我们可以将原图缩点简化问题。经过tarjan缩点后,得到了一个DAG,我们很容易想到如果一个图“分叉”,那么分叉上的点肯定是互不可达的,所以原图必须是“一条链”。为什么对链打引号呢?是因为这条链不用很严格,比如1->2 2->3 3->4 1->4 2->4这样的图也是满足的,且可能还有重边,所以只用入度出度去判断的方法容易出错,dfs去走一条链的办法也比较麻烦(我没想出有什么简单的办法)。我们利用拓扑排序,若一直都是一个入度为0的点(队中至多有一个点),那么就是满足题意的。
Ps:可以先写一个floyd来对拍(因为时间代价很低)
时间复杂度O(n+m)
代码如下:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <string> using namespace std; const int maxn = 100010; const int maxm = 400010; struct edge { int v,next; }e[maxm]; int h[maxn],num; int indexx; int top,stack[maxn]; int belong[maxn],cnt; int hh[maxn],numm; int q[maxn],head,tail; int u,v,n,m; bool vis[maxn]; int p[maxn],dfn[maxn],low[maxn]; int T; void dfs(int u) //tarjan { int v; indexx++; dfn[u] = indexx; low[u] = indexx; vis[u] = true; stack[++top] = u; for(int i = h[u]; i != 0; i = e[i].next) { v = e[i].v; if(!dfn[v]) { dfs(v); if(low[v] < low[u]) low[u] = low[v]; } else if(vis[v] && dfn[v] < low[u]) low[u] = dfn[v]; } if(dfn[u] == low[u]) { cnt++; while(1) { int x = stack[top]; vis[x] = false; belong[x] = cnt; top--; if(x == u) break; } } } void build(int u,int v) //原图建边 { num++; e[num].v = v; e[num].next = h[u]; h[u] = num; } void build2(int u,int v) //缩点后的图建边 { num++; e[num].v = v; e[num].next = hh[u]; hh[u] = num; } int main() { freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); num = cnt = indexx = 0; memset(h,0,sizeof(h)); memset(hh,0,sizeof(hh)); while(m--) { scanf("%d%d",&u,&v); build(u,v); } memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(p,0,sizeof(p)); //记住初始化 for(int i = 1; i <= n; i++) if(!dfn[i]) dfs(i); for(int i = 1; i <= n; i++) { for(int j = h[i]; j; j = e[j].next) if(belong[i] != belong[e[j].v]) //不是一个SCC的就建边 { p[belong[e[j].v]]++; build2(belong[i],belong[e[j].v]); } } bool ans = true; head = tail = 0; for(int i = 1; i <= cnt; i++) //统计入度为0的 if(!p[i]) q[tail++] = i; while(head < tail) { if(tail - head > 1) //队中有超过两个点,就说明不满足条件 { ans = false; break; } u = q[head]; for(int i = hh[u]; i; i = e[i].next) { v = e[i].v; p[v]--; if(p[v] == 0) q[tail++] = v; } head++; } if(ans) printf("Yes\n"); else printf("No\n"); } return 0; }
下面是我自己的代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 1000010; const int maxm = 400010; struct edge { int v,next; }e[maxm]; int h[maxn],num; int indexx; int top,stack[maxn]; int belong[maxn],cnt; int hh[maxn],numm; int q[maxn],head,tail; int u,v,n,m; bool vis[maxn]; int p[maxn],dfn[maxn],low[maxn]; int T; void dfs(int u) //tarjan { int v; indexx++; dfn[u]=indexx; low[u]=indexx; vis[u]=true; stack[++top] = u; for(int i=h[u];i!=0;i=e[i].next) { v=e[i].v; if(!dfn[v]) { dfs(v); if(low[v] < low[u]) low[u] = low[v]; } else if(vis[v] && dfn[v] < low[u]) low[u] = dfn[v]; } if(dfn[u]==low[u]) { cnt++; while(1) { int x = stack[top]; vis[x] = false; belong[x] = cnt; top--; if(x == u) break; } } } void build(int u,int v) //原图建边 { num++; e[num].v=v; e[num].next=h[u]; h[u]=num; } void build2(int u,int v) //缩点后的图建边 { num++; e[num].v=v; e[num].next=hh[u]; hh[u]=num; } int main() { freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); num=cnt=indexx=0; memset(h,0,sizeof(h)); memset(hh,0,sizeof(hh)); while(m--) { scanf("%d%d",&u,&v); build(u,v); } memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(p,0,sizeof(p)); //记住初始化 for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); for(int i=1;i<=n;i++) for(int j=h[i];j;j=e[j].next) if(belong[i]!=belong[e[j].v]) //不是一个SCC的就建边 { p[belong[e[j].v]]++; build2(belong[i],belong[e[j].v]); } bool ans=true; head=tail=0; for(int i=1; i<=cnt;i++) //统计入度为0的 if(!p[i]) q[tail++] = i; while(head<tail) { if(tail-head>1) //队中有超过两个点,就说明不满足条件 { ans=false; break; } u=q[head]; for(int i=hh[u];i;i=e[i].next) { v=e[i].v; p[v]--; if(p[v]==0) q[tail++] = v; } head++; } if(ans) printf("Yes\n"); else printf("No\n"); } return 0; }
【解题报告】
最朴素的做法:枚举航空公司,将本公司的航线加入,然后做一遍MST,时间复杂度是O(klogk+mk)的。
但我们注意到“在最小生成树中任意一条边都是连接两个集合边权最小的一条边”,这个是MST一个非常重要的结论,是Prim和kruscal算法中贪心的核心。利用这条性质我们可以知道,对于每个航空公司,我们只需要原图MST中得到的航线即可得到最优购买方案。所以更优的做法是:先跑一次MST,把得到的边记录下来,然后枚举航空公司,还是先讲本公司的边全部加进去,然后只用扫一边第一次MST中记录下来的边,用kruscal的方法去构造树即可。时间复杂度为O(klogk+nm)。
对于本题,50%的数据是可以O(mk)过的,最后30%的数据我特别构造了一下,必须枚举到排序后的最后几条边才能构造出一颗生成树,所以O(mk)应该是过不了的,此外的20%数据是随机数据,加了构造出树就跳出判断的代码可能会过(但是jyb写的mk的没过。。);另外,50%的数据需要long long
代码如下:
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <cstdlib> #include <algorithm> #include <iostream> using namespace std; const int maxm = 200002; const int maxn = 2002; const long long INF = 1ll << 60; struct line { int u,v; long long w; bool operator < (const line &b) const { return w < b.w; } }l[maxm],mst[maxn]; struct edge { int u,v,next; }e[maxm]; int h[maxn],num; int fa[maxn]; long long a[maxn]; int n,m,k,cnt; long long ans; void build(int no,int u,int v) { num++; e[num].u = u; e[num].v = v; e[num].next = h[no]; h[no] = num; } int getfather(int x) { if(x == fa[x]) return x; else return fa[x] = getfather(fa[x]); } int main() { freopen("airplane.in","r",stdin); freopen("airplane.out","w",stdout); scanf("%d%d%d",&n,&m,&k); for(int i = 1; i <= m; i++) scanf("%I64d",&a[i]); int u,v,no,w; for(int i = 1; i <= k; i++) { scanf("%d%d%d%d",&u,&v,&w,&no); l[i].u = u; l[i].v = v; l[i].w = w; build(no,u,v); } sort(l+1,l+1+k); for(int i = 1; i <= n; i++) fa[i] = i; for(int i = 1; i <= k; i++) { int x = getfather(l[i].u); int y = getfather(l[i].v); if(x != y) { fa[y] = x; mst[++cnt] = l[i]; } } ans = INF; for(int i = 1; i <= m; i++) { for(int j = 1; j <= n; j++) fa[j] = j; for(int j = h[i]; j; j = e[j].next) { int x = getfather(e[j].u); int y = getfather(e[j].v); if(x != y) fa[y] = x; } long long res = 0; for(int j = 1; j <= cnt; j++) { int x = getfather(mst[j].u); int y = getfather(mst[j].v); if(x != y) { fa[y] = x; res += mst[j].w; } } if(res + a[i] < ans) ans = res + a[i]; } printf("%I64d\n",ans); return 0; }
相关文章推荐
- 图的遍历(dfs、bfs、最短路、最小生成树、拓扑排序)
- 数据结构实验报告-图算法-最小生成树-最短路-拓扑排序-搜索
- 并查集和拓扑排序加最小生成树
- 最小生成树 最短路 并查集
- 最短路,最小生成树,及拓扑排序模板整理
- 图论知识整理(2) 拓扑排序、最短路、最小生成树
- 「数据结构作业」最短路,拓扑排序,关键路径,最小生成树
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】
- 拓扑排序 详解 + 并查集 详解 + 最小生成树详解
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解
- hdu 1863 畅通工程 最小生成树+并查集
- 最小生成树 POJ——Truck History(没看懂题,直接根据测试用例YY的,我的Kusal 写法复习)
- 最小生成树之Kruskal(并查集实现)
- hdu1863(最小生成树,并查集)
- Kruscal算法+并查集 求解最小生成树
- Kruscal算法 并查集求解最小生成树
- poj 1258 并查集的基本应用 最小生成树
- 并查集最小生成树复习
- 图论小结(一)包括一些最短路,最小生成树,差分约束,欧拉回路,的经典题和变种题。强连通,双连通,割点割桥的应用。二分匹配,KM,支配集,独立集,还有2-SAT。