2017.4.3 机房测试 (并查集,最短路,拓扑排序,最小生成树)

来源:互联网 发布:不出于户 以知天下 编辑:程序博客网 时间:2024/05/01 09:13

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))

代码如下:

#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 100000int 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[n];}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;}
0 0
原创粉丝点击