第六次练习赛解题报告及标程

  这次练习赛的出题其实非常有针对性,只可惜根据我的调查,体会到这一点的童鞋并不多,好伤心……

  A. Who Are My Friends?(Ⅰ)
  B. Who Are My Friends?(Ⅱ)

  两道上机原题,见上机题解

  C. Who Are My Friends?(Ⅲ)

  我们注意到这道题与前两题的唯一区别是,查询过程是在线的,也就是说,边修改边查询。也正因为这个原因,这道题不能再用图论的方法dfs。而并查集的操作上则与之前没有什么不同。

#include<cstdio>
using namespace std;
const int MAXN=100005;
int u[MAXN];
void init()
{
    for(int i=0; i<MAXN; ++i)
        u[i]=i;
}
int find(int x)
{
    if(u[x]!=x)
        u[x]=find(u[x]);
    return u[x];
}
void merge(int x,int y)
{
    u[find(x)]=find(y);
}
bool query(int x,int y)
{
    return find(x)==find(y);
}
int main()
{
    int n,k,a,b;
    while(~scanf("%d",&n))
    {
        init();
        while(n--)
        {
            scanf("%d%d%d",&k,&a,&b);
            switch(k)
            {
            case 1:
                merge(a,b);
                break;
            case 2:
                puts(query(a,b)?"Great!":"Pity...");
            }
        }
    }
}
#include<cstdio>
using namespace std;
const int MAXN=100005;
int u[MAXN],r[MAXN];
void init()
{
    for(int i=0; i<MAXN; ++i)
    {
        u[i]=i;
        r[i]=0;
    }
}
int find(int x)
{
    if(u[x]!=x)
        return find(u[x]);
    return u[x];
}
void merge(int x,int y)
{
    x=find(x);
    y=find(y);
    if(r[x]>r[y])
        u[y]=x;
    else
    {
        u[x]=y;
        if(r[x]==r[y])
            ++r[y];
    }
}
bool query(int x,int y)
{
    return find(x)==find(y);
}
int main()
{
    int n,k,a,b;
    while(~scanf("%d",&n))
    {
        init();
        while(n--)
        {
            scanf("%d%d%d",&k,&a,&b);
            switch(k)
            {
            case 1:
                merge(a,b);
                break;
            case 2:
                puts(query(a,b)?"Great!":"Pity...");
            }
        }
    }
}

  D. 图的DFS遍历

  这道题有点无聊,可以说是为了卡内存而卡内存。除了动态管理内存之外,还可以用vector来模拟链表。剩下的就是链表中每个点对应的边应该排好序,然后dfs即可。我直接使用了优先队列和set两种结构来代替vector,用来维护有序性。

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=1005;
priority_queue<int,vector<int>,greater<int> > g[MAXN];
bool vis[MAXN];
void dfs(int u)
{
    vis[u]=true;
    printf("%d ",u);
    while(!g[u].empty())
    {
        int v=g[u].top();
        g[u].pop();
        if(!vis[v])
            dfs(v);
    }
}
int main()
{
    int n,k,m,u,v;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&k,&m);
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u].push(v);
            g[v].push(u);
        }
        memset(vis,false,sizeof(vis));
        for(int i=0; i<k; ++i)
            if(!vis[i])
                dfs(i);
        putchar('\n');
    }
}
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int MAXN=1005;
set<int> g[MAXN];
bool vis[MAXN];
void dfs(int u)
{
    vis[u]=true;
    printf("%d ",u);
    for(set<int>::iterator v=g[u].begin(); v!=g[u].end(); ++v)
        if(!vis[*v])
            dfs(*v);
}
int main()
{
    int n,k,m,u,v;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&k,&m);
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u].insert(v);
            g[v].insert(u);
        }
        memset(vis,false,sizeof(vis));
        for(int i=0; i<k; ++i)
            if(!vis[i])
                dfs(i);
        putchar('\n');
        for(int i=0; i<k; ++i)
            g[i].clear();
    }
}

  E. 蒹葭苍苍

  单点到单点的最短路,bfs即可。因为有多次询问,写floyd也是一个办法,只是因为询问次数比较少,相比前者会慢一些。我闲得蛋疼写了三份代码。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=305;
bool g[MAXN][MAXN],vis[MAXN];
int n;
int bfs(int a,int b)
{
    queue<pair<int,int> > q;
    vis[a]=true;
    q.push(make_pair(a,0));
    while(!q.empty())
    {
        pair<int,int> u=q.front();
        q.pop();
        for(int v=1; v<=n; ++v)
            if(g[u.first][v])
            {
                if(v==b)
                    return u.second;
                if(!vis[v])
                {
                    vis[v]=true;
                    q.push(make_pair(v,u.second+1));
                }
            }
    }
    return -1;
}
int main()
{
    int m,k,u,v;
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        memset(g,false,sizeof(g));
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u][v]=g[v][u]=true;
        }
        while(k--)
        {
            scanf("%d%d",&u,&v);
            memset(vis,false,sizeof(vis));
            printf("%d\n",bfs(u,v));
        }
    }
}
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=305;
const int MAXM=20005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
bool vis[MAXN];
int bfs(int a,int b)
{
    queue<pair<int,int> > q;
    vis[a]=true;
    q.push(make_pair(a,0));
    while(!q.empty())
    {
        pair<int,int> u=q.front();
        q.pop();
        for(int i=g.head[u.first]; ~i; i=g.next[i])
        {
            int v=g.to[i];
            if(v==b)
                return u.second;
            if(!vis[v])
            {
                vis[v]=true;
                q.push(make_pair(v,u.second+1));
            }
        }
    }
    return -1;
}
int main()
{
    int n,m,k,u,v;
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
            g.add(v,u);
        }
        while(k--)
        {
            scanf("%d%d",&u,&v);
            memset(vis,false,sizeof(vis));
            printf("%d\n",bfs(u,v));
        }
    }
}
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=305;
const int INF=0x3f3f3f3f;
int g[MAXN][MAXN],n,m,k,u,v;
void floyd()
{
    for(int k=1; k<=n; ++k)
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=n; ++j)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
int main()
{
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        memset(g,0x3f,sizeof(g));
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u][v]=g[v][u]=1;
        }
        floyd();
        while(k--)
        {
            scanf("%d%d",&u,&v);
            printf("%d\n",g[u][v]==INF?-1:g[u][v]-1);
        }
    }
}

  F. 图的环路(Ⅰ)

  图的判环是一个很基础的问题,有很多种方式。对于无向图,我这里给出两种方法。

  一种是遍历,在遍历过程中如果遇到已经访问过的节点则意味着有环。至于使用dfs还是bfs并不重要。

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
bool vis[MAXN];
bool dfs(int u,int c)
{
    vis[c]=true;
    bool flag=false;
    for(int i=g.head[c]; !flag&&~i; i=g.next[i])
    {
        int v=g.to[i];
        if(v!=u)
        {
            if(vis[v])
                return true;
            flag=dfs(c,v);
        }
    }
    return flag;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
            g.add(v,u);
        }
        memset(vis,false,sizeof(vis));
        bool flag=false;
        for(int i=1; !flag&&i<=n; ++i)
            if(!vis[i])
                flag=dfs(0,i);
        puts(flag?"Yes":"No");
    }
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
bool vis[MAXN];
bool dfs(int u,int c)
{
    vis[c]=true;
    bool flag=false;
    for(int i=0; !flag&&i<g[c].size(); ++i)
    {
        int v=g[c][i];
        if(v!=u)
        {
            if(vis[v])
                return true;
            flag=dfs(c,v);
        }
    }
    return flag;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        memset(vis,false,sizeof(vis));
        bool flag=false;
        for(int i=1; !flag&&i<=n; ++i)
            if(!vis[i])
                flag=dfs(0,i);
        puts(flag?"Yes":"No");
        for(int i=1; i<=n; ++i)
            g[i].clear();
    }
}

  一种是并查集,不断合并有边相连的两个点,如果合并前两个点已经在一个集合内,则意味着他们在一个环里。

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
int f[MAXN];
void init()
{
    for(int i=0; i<MAXN; ++i)
        f[i]=i;
}
int find(int x)
{
    if(f[x]!=x)
        f[x]=find(f[x]);
    return f[x];
}
void merge(int x,int y)
{
    f[find(x)]=find(y);
}
bool query(int x,int y)
{
    return find(x)==find(y);
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        bool flag=false;
        init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            if(query(u,v))
                flag=true;
            merge(u,v);
        }
        puts(flag?"Yes":"No");
    }
}

  G. 图的环路(Ⅱ)

  对于有向图,这里也给出两种做法。一种仍然是遍历,由于遍历到的已访问过的点不一定意味着成环,只有当这个点是这次遍历的祖先节点时才成环,所以记录状态时要多一个状态:0表示尚未访问,-1表示正在访问子节点,1表示已访问过所有子节点。

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
int vis[MAXN];
bool dfs(int u)
{
    vis[u]=-1;
    bool flag=false;
    for(int i=g.head[u]; !flag&&~i; i=g.next[i])
    {
        int v=g.to[i];
        if(!~vis[v])
            return true;
        if(!vis[v])
            flag=dfs(v);
    }
    vis[u]=1;
    return flag;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
        }
        memset(vis,0,sizeof(vis));
        bool flag=false;
        for(int i=1; !flag&&i<=n; ++i)
            if(!vis[i])
                flag=dfs(i);
        puts(flag?"Yes":"No");
    }
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
int vis[MAXN];
bool dfs(int u)
{
    vis[u]=-1;
    bool flag=false;
    for(int i=0; !flag&&i<g[u].size(); ++i)
    {
        int v=g[u][i];
        if(!~vis[v])
            return true;
        if(!vis[v])
            flag=dfs(v);
    }
    vis[u]=1;
    return flag;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
        }
        memset(vis,0,sizeof(vis));
        bool flag=false;
        for(int i=1; !flag&&i<=n; ++i)
            if(!vis[i])
                flag=dfs(i);
        puts(flag?"Yes":"No");
        for(int i=1; i<=n; ++i)
            g[i].clear();
    }
}

  另外拓扑排序可以判环。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
int du[MAXN],n;
bool toposort()
{
    memset(du,0,sizeof(du));
    for(int i=1; i<=n; ++i)
        for(int j=g.head[i]; ~j; j=g.next[j])
            ++du[g.to[j]];
    int tot=0;
    queue<int> q;
    for(int i=1; i<=n; ++i)
        if(!du[i])
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        ++tot;
        for(int i=g.head[u]; ~i; i=g.next[i])
        {
            int v=g.to[i];
            if(!(--du[v]))
                q.push(v);
        }
    }
    return tot==n;
}
int main()
{
    int m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
        }
        puts(!toposort()?"Yes":"No");
    }
}
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
int du[MAXN],n;
bool toposort()
{
    memset(du,0,sizeof(du));
    for(int i=1; i<=n; ++i)
        for(int j=0; j<g[i].size(); ++j)
            ++du[g[i][j]];
    int tot=0;
    queue<int> q;
    for(int i=1; i<=n; ++i)
        if(!du[i])
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        ++tot;
        for(int i=0; i<g[u].size(); ++i)
        {
            int v=g[u][i];
            if(!(--du[v]))
                q.push(v);
        }
    }
    return tot==n;
}
int main()
{
    int m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
        }
        puts(!toposort()?"Yes":"No");
        for(int i=1; i<=n; ++i)
            g[i].clear();
    }
}

  H. Draught

  有童鞋过掉这道题还是挺开心的……这道题是CodeChef上的一道原题,算是一个比较弱的树形dp,或者一个比较难的遍历题。第一个问很好做,对于每个连通子图记录节点个数即可;第二个问,节点有气流穿过无非是两种情况,祖先至少有一个节点开窗且子树至少有一个节点开窗,或者子树有至少两个节点开窗,其中子树包括这个节点。两个问可以两次遍历分开求,也可以一次遍历一起做,两种写法稍有不同,代码都会放上来。注意在适当的地方用long long。

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=50005;
const int MAXM=100005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
bool win[MAXN],air[MAXN];
int vis[MAXN],wcnt;
void dfs1(int u)
{
    vis[u]=1;
    if(win[u])
        ++wcnt;
    for(int i=g.head[u]; ~i; i=g.next[i])
    {
        int v=g.to[i];
        if(vis[v]==0)
            dfs1(v);
    }
}
int dfs2(int u)
{
    int ret=0,cnt=0;
    vis[u]=2;
    if(win[u])
        ++ret;
    for(int i=g.head[u]; ~i; i=g.next[i])
    {
        int v=g.to[i];
        if(vis[v]==1)
        {
            int tmp=dfs2(v);
            if(tmp>0)
                ++cnt;
            ret+=tmp;
        }
    }
    if(cnt>=2||(cnt>=1&&win[u])||(ret>0&&wcnt>ret))
        air[u]=true;
    return ret;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<=n; ++i)
            scanf("%d",&win[i]);
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
            g.add(v,u);
        }
        memset(vis,0,sizeof(vis));
        memset(air,false,sizeof(air));
        int fans=0,rans=0;
        for(int i=1; i<=n; ++i)
        {
            if(vis[i]==0)
            {
                wcnt=0;
                dfs1(i);
                fans+=(long long)wcnt*(wcnt-1)/2;
                dfs2(i);
            }
            if(air[i])
                ++rans;
        }
        printf("%d %d\n",fans,rans);
    }
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=50005;
vector<int> E[MAXN];
bool win[MAXN],air[MAXN];
int vis[MAXN],wcnt;
void dfs1(int u)
{
    vis[u]=1;
    if(win[u])
        ++wcnt;
    for(int v=0; v<E[u].size(); ++v)
        if(vis[E[u][v]]==0)
            dfs1(E[u][v]);
}
int dfs2(int u)
{
    int ret=0,cnt=0;
    vis[u]=2;
    if(win[u])
        ++ret;
    for(int v=0; v<E[u].size(); ++v)
        if(vis[E[u][v]]==1)
        {
            int tmp=dfs2(E[u][v]);
            if(tmp>0)
                ++cnt;
            ret+=tmp;
        }
    if(cnt>=2||(cnt>=1&&win[u])||(ret>0&&wcnt>ret))
        air[u]=true;
    return ret;
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<=n; ++i)
            scanf("%d",&win[i]);
        while(m--)
        {
            scanf("%d%d",&u,&v);
            E[u].push_back(v);
            E[v].push_back(u);
        }
        memset(vis,0,sizeof(vis));
        memset(air,false,sizeof(air));
        int fans=0,rans=0;
        for(int i=1; i<=n; ++i)
        {
            if(vis[i]==0)
            {
                wcnt=0;
                dfs1(i);
                fans+=(long long)wcnt*(wcnt-1)/2;
                dfs2(i);
            }
            if(air[i])
                ++rans;
        }
        printf("%d %d\n",fans,rans);
        for(int i=1; i<=n; ++i)
            E[i].clear();
    }
}
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=50005;
const int MAXM=100005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int x,int y)
    {
        to[tot]=y;
        next[tot]=head[x];
        head[x]=tot++;
    }
} g;
bool win[MAXN],vis[MAXN],air[MAXN];
int src;
int dfs(int u)
{
    int ret=0;
    vis[u]=true;
    for(int i=g.head[u]; ~i; i=g.next[i])
    {
        int v=g.to[i];
        if(!vis[v])
            ret+=dfs(v);
    }
    if(ret>0||(u!=src&&win[u]))
        air[u]=true;
    return ret+win[u];
}
int main()
{
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<=n; ++i)
            scanf("%d",&win[i]);
        g.init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            g.add(u,v);
            g.add(v,u);
        }
        memset(vis,false,sizeof(vis));
        memset(air,false,sizeof(air));
        int fans=0,rans=0;
        for(int i=1; i<=n; ++i)
            if(!vis[i]&&win[i])
            {
                src=i;
                long long wcnt=dfs(i);
                fans+=wcnt*(wcnt-1)/2;
            }
        for(int i=1; i<=n; ++i)
            if(air[i])
                ++rans;
        printf("%d %d\n",fans,rans);
    }
}

  总得来说,这次练习赛重点比较了图论和并查集的相似点和不同点,练习了图论的遍历,加深对图论概念的理解(无向图、有向图、环等),也有一定挑战性的题。至于从中能得到多少收获,全看童鞋们自己了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值