现在的位置: 首页 > 综合 > 正文

第六周周赛 lca 强连通 最小生成树 最短路

2019年02月08日 ⁄ 综合 ⁄ 共 9871字 ⁄ 字号 评论关闭

1008:

hdu2544

floy算法o(n^3),注意下最外层循环是k

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#define INF 0x7fffffff
#define maxn 105
#define maxl 0x7fffffff
#define fi for(int i=0;i<n;i++)
#define fj for(int j=0;j<n;j++)
#define wh while(t--)
using namespace std;
long long map[maxn][maxn];
int main()
{
    int n,m,a,b,c;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(m==0&&n==0) break;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i==j)
                map[i][j]=0;
                else
                map[i][j]=INF;
            }
        }
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            map[a][b]=c;
            map[b][a]=c;
        }
        for(int k=1;k<=n;k++)
        {
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=n;j++)
                {
                    if(map[i][k]!=INF&&map[k][j]!=INF)
                    {
                        if(map[i][k]+map[k][j]<map[i][j])
                        map[i][j]=map[i][k]+map[k][j];
                    }
                }
            }
        }
        printf("%lld\n",map[1][n]);
    }
    return 0;
}

1007:

hdu1102

最小生成树,注意存边,将已经连好的边的权值为,从长度最小的边开始连,判断是否在同一个集合中,不在则连起来,直到所有的点都连起来。

代码:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define maxn 10005
using namespace std;
struct p
{
    int s,e,w;
} edg[maxn];
int f[maxn];
bool cmp(p x,p y)
{
    return x.w<y.w;
}
int find(int x)
{
    return x==f[x]?x:(f[x]=find(f[x]));
}
void connect(int x,int y)
{
    f[x]=y;
}
int kruscal(int n,int num)
{
    int sum=0;
    for(int i=1; i<=n; i++)
    {
        f[i]=i;
    }
    for(int i=0; i<num; i++)
    {
        int a=find(edg[i].s),b=find(edg[i].e);
        if(a!=b)
        {
            connect(a,b);
            sum+=edg[i].w;
        }
    }
    return sum;
}
int main()
{
    int c,num=0,n;
    while(scanf("%d",&n)!=EOF)
    {
        num=0;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                scanf("%d",&c);
                if(i!=j)
                {
                    edg[num].s=i,edg[num].e=j,edg[num].w=c;
                    num++;
                }
            }
        }
        int m,a,b;
        scanf("%d",&m);
        for(int i=0; i<m; i++)
        {
            scanf("%d%d",&a,&b);
            for(int i=0; i<num; i++)
            {
                if(edg[i].s==a&&edg[i].e==b)
                {
                    edg[i].w=0;
                }
                if(edg[i].e==a&&edg[i].s==b)
                {
                    edg[i].w=0;
                }
            }
        }
        sort(edg,edg+num,cmp);
        printf("%d\n",kruscal(n,num));
    }
    return 0;
}

1006:

hdu1827

这题是找入度为0的强连通分量,用到了缩点。这里用了一个g【】【】来存反向边,从而判断入度。也可以用两个数组分别存缩点的入度和出度。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#include<cstring>
#define maxn 1005
using namespace std;
int n,m,index,ans,mcost;
int cost[maxn],vis[maxn],in[maxn],dfn[maxn],low[maxn];
stack<int> Stack;

vector<int> belong[maxn];//保存各个连通块的元素
int be[maxn];//记录每个元素属于哪个连通块
int mincost[maxn];//记录每个连通块的最小费用
vector<int> g[maxn];

vector<int> ed[maxn];
int min(int x,int y)
{
    return x<y?x:y;
}
void init()
{
    for(int i=1;i<=n;i++)
    {
        ed[i].clear();
        belong[i].clear();
        g[i].clear();
    }
    memset(vis,0,sizeof(vis));
    memset(in,0,sizeof(in));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(be,0,sizeof(be));
    memset(mincost,0,sizeof(mincost));
}
void tarjan(int u)
{
    vis[u]=1;
    in[u]=1;
    dfn[u]=low[u]=++index;
    Stack.push(u);

    int size=ed[u].size();
    for(int i=0;i<size;i++)
    {
        int v=ed[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(in[v])
        {
            low[u]=min(dfn[v],low[u]);
        }
    }
    int m=0;
    if(dfn[u]==low[u])
    {
        ans++;
        int v=Stack.top();
        Stack.pop();
        
        be[v]=ans;
        belong[ans].push_back(v);

        in[v]=0;
        m=min(cost[u],cost[v]);
        while(u!=v)
        {
            v=Stack.top();
            Stack.pop();

            be[v]=ans;
            belong[ans].push_back(v);

            in[v]=0;
            m=min(m,cost[v]);
        }
        mincost[ans]=m;
    }
}
void solve()
{
    int sum=ans,t;
    for(int i=1;i<=sum;i++)
    {
        t=0;
        int len=belong[i].size();
        for(int j=0;j<len;j++)
        {
            int u=belong[i][j];
            int size=g[u].size();
            for(int k=0;k<size;k++)
            {
                int v=g[u][k];
                if(be[v]!=i)//如果有入度
                {
                    t=1;
                    ans--;
                    break;
                }
            }
            if(t)//如果有入度
            {
                break;
            }
        }
        if(!t)//如果没有入度
        {
            mcost+=mincost[i];
        }
    }
}
int main()
{
    int a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        init();
        for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            ed[a].push_back(b);

            g[b].push_back(a);
        }
        ans=0,mcost=0;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                index=0;
                tarjan(i);
            }
        }
        solve();
        printf("%d %d\n",ans,mcost);
    }
    return 0;
}

1005

hdu1269

强连通模版题,判断是否为强连通图,即只有一个强连通分量。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#define INF 0x7fffffff
#define maxn 100005
#define maxl 10005
#define fi for(int i=0;i<n;i++)
#define fj for(int j=0;j<n;j++)
#define wh while(t--)
using namespace std;
vector<int> ed[maxn];
stack<int> Stack;
int vis[maxl],dfn[maxl],low[maxl],vi[maxl];
int ans,index;
int n,m;
int min(int x,int y)
{
    return x<y?x:y;
}
void init()
{
    for(int i=1;i<=n;i++)
    {
        ed[i].clear();
    }
    memset(vis,0,sizeof(vis));
    memset(dfn,0,sizeof(vis));
    memset(low,0,sizeof(vis));
    memset(vi,0,sizeof(vis));
}
void tarjan(int u)
{
    vis[u]=1;//在栈里
    vi[u]=1;//被访问
    dfn[u]=low[u]=++index;//从1开始
    Stack.push(u);
    int size=ed[u].size();
    for(int i=0;i<size;i++)
    {
        int v=ed[u][i];
        if(!vi[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u])
    {
        ans++;
        int v=Stack.top();
        Stack.pop();
        vis[v]=0;
        while(u!=v)
        {
            v=Stack.top();
            Stack.pop();
            vis[v]=0;
        }
    }
}
int main()
{
    int a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            ed[a].push_back(b);
            //ed[b].push_back(a);
        }
        ans=0;
        for(int i=1;i<=n;i++)
        {
            if(!vi[i])
            {
                while(!Stack.empty())
                {
                    Stack.pop();
                }
                index=0;
                tarjan(i);
            }
        }
        //cout<<ans<<endl;

        if(ans==1)
        printf("Yes\n");
        else
        printf("No\n");

    }
    return 0;
}

1004:

hdu2767

构建强连通图,缩点后,增加的边数为,入度和出度的最大值(因为强连通图中所有点的入度和出度都不为0,只要使最大的不为0,则另一个必为0)

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#include<cstring>
#define maxn 20005
using namespace std;
int n,m,index,ans;
int IN,TO,from[maxn],too[maxn];
int vis[maxn],in[maxn],dfn[maxn],low[maxn],be[maxn];
vector<int> ed[maxn];
stack<int> Stack;
int max(int x,int y)
{
    return x>y?x:y;
}
int min(int x,int y)
{
    return x<y?x:y;
}
void init()
{
    for(int i=1;i<=n;i++)
    {
        ed[i].clear();
    }
    memset(vis,0,sizeof(vis));
    memset(in,0,sizeof(in));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(be,0,sizeof(be));
    memset(from,0,sizeof(from));
    memset(too,0,sizeof(too));
}
void tarjan(int u)
{
    vis[u]=1;
    dfn[u]=low[u]=++index;
    in[u]=1;
    Stack.push(u);

    int size=ed[u].size();
    for(int i=0;i<size;i++)
    {
        int v=ed[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(in[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u])
    {
        ans++;
        int v=-1;
        while(u!=v)
        {
            v=Stack.top();
            Stack.pop();
            in[v]=0;
            be[v]=ans;
        }
    }
}
void solve()
{
    for(int i=1;i<=n;i++)
    {
        int size=ed[i].size();
        for(int j=0;j<size;j++)
        {
            int v=ed[i][j];
            if(be[v]!=be[i])
            {
                from[be[i]]++;//出度
                too[be[v]]++;//入度
            }
        }
    }
    IN=0,TO=0;
    for(int i=1;i<=ans;i++)
    {
        if(from[i]==0) TO++;//出度
        if(too[i]==0) IN++;//入度
    }
}
int main()
{
    int t,a,b;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            ed[a].push_back(b);
        }
        ans=0;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                while(!Stack.empty())
                {
                    Stack.pop();
                }
                index=0;
                tarjan(i);
            }
        }
        if(ans==1)
        {
            printf("0\n");
            continue;
        }
        IN=0,TO=0;
        solve();
        printf("%d\n",max(IN,TO));
    }
    return 0;
}

1003 

hdu2586

这题所有点都连通,存双向边,利用数组存每个询问的答案。

注意的是G++栈比较大,C++栈开的小,要手动开大栈

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#define INF 0x7fffffff
#define maxn 40005
#define maxl 205
#pragma comment(linker,"/STACK:102400000,102400000") //手动开大栈区
#define fi for(int i=0;i<n;i++)
#define fj for(int j=0;j<n;j++)
#define wh while(t--)
using namespace std;
struct ed
{
    int v,l;
};
vector<ed>g[maxn];//表示每个点开始的边
vector<ed>q[maxn];//表示询问的点对
int vis[maxn];      //记录每个点是否被访问
int dis[maxn];      //记录每个点的深度
int ans[maxl];      //记录没个询问的答案
int f[maxn];        //记录没个结点的父亲
//int num;
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        g[i].clear();
        q[i].clear();
        f[i]=i;//初始化每个结点的father
    }
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(ans,-1,sizeof(ans));
}
void add(int u,int v,int l)
{
    ed ned;
    ned.v=v,ned.l=l;
    g[u].push_back(ned);
}
void adde(int u,int v,int i)
{
    ed ned;
    ned.v=v,ned.l=i;//标记是第几条边
    q[u].push_back(ned);

}
int find(int x)
{
    if(x==f[x])
    return x;

    return f[x]=find(f[x]);
}
void tarjan(int u,int l)
{
    dis[u]=l;//当前的深度为l
    vis[u]=1;//num;//标记当前的点已经遍历了,因为是双向边所以先记录

    int size=g[u].size();
    for(int i=0;i<size;i++)
    {
        int v=g[u][i].v;
        int ll=g[u][i].l;
        if(!vis[v])
        {
            tarjan(v,l+ll);
            f[find(v)]=find(u);
        }
    }
    size=q[u].size();
    for(int i=0;i<size;i++)
    {
        int v=q[u][i].v;
        if(vis[v])
        {
            //if(vis[v]==num)//如果v已经在这棵树种被访问了
            ans[q[u][i].l]=dis[u]+dis[v]-2*dis[find(v)];
        }
    }
}
int main()
{
    int t;
    int n,m,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        init(n);
        for(int i=0;i<n-1;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            //双向边
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=0;i<k;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            adde(a,b,i);
            adde(b,a,i);
        }
        //num=0;//标记是第几棵树
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                //num++;//标记是第棵树
                tarjan(i,0);//传递根和深度
            }
        }
        for(int i=0;i<k;i++)
        {
            //if(ans[i]==-1)
            //printf("Not connected\n");
            //cout<<"Not connected\n";
            //else
            //cout<<ans[i]<<endl;
            printf("%d\n",ans[i]);
        }
        //printf("\n");
    }
	return 0;
}

1002 lca

hdu2874

http://acm.hdu.edu.cn/showproblem.php?pid=2874

这题想了很久,对并查集也有了新的了解(压缩路径,每次查找祖先时把遍历的路上的所有点的祖先都设置为最后的那个点)。

接下来就是对lca离线算法的认识,对于树,森林,图,求距离,还是求公共祖先。

这题就是求最短距离=dis[u]+dis[v]-dis[find(v)];

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#define INF 0x7fffffff
#define maxn 10005
#define maxl 1000005
#define fi for(int i=0;i<n;i++)
#define fj for(int j=0;j<n;j++)
#define wh while(t--)
using namespace std;
struct ed
{
    int v,l;
};
vector<ed>g[maxn];//表示每个点开始的边
vector<ed>q[maxn];//表示询问的点对
int vis[maxn];      //记录每个点是否被访问
int dis[maxn];      //记录每个点的深度
int ans[maxl];      //记录没个询问的答案
int f[maxn];        //记录没个结点的父亲
int num;
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        g[i].clear();
        q[i].clear();
        f[i]=i;//初始化每个结点的father
    }
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(ans,-1,sizeof(ans));
}
void add(int u,int v,int l)
{
    ed ned;
    ned.v=v,ned.l=l;
    g[u].push_back(ned);
}
void adde(int u,int v,int i)
{
    ed ned;
    ned.v=v,ned.l=i;//标记是第几条边
    q[u].push_back(ned);

}
int find(int x)
{
    if(x==f[x])
    return x;

    return f[x]=find(f[x]);
}
void tarjan(int u,int l)
{
    dis[u]=l;//当前的深度为l
    vis[u]=num;//标记当前的点已经遍历了,因为是双向边所以先记录

    int size=g[u].size();
    for(int i=0;i<size;i++)
    {
        int v=g[u][i].v;
        int ll=g[u][i].l;
        if(!vis[v])
        {
            tarjan(v,l+ll);
            f[find(v)]=find(u);
        }
    }
    size=q[u].size();
    for(int i=0;i<size;i++)
    {
        int v=q[u][i].v;
        if(vis[v])
        {
            if(vis[v]==num)//如果v已经在这棵树种被访问了
            ans[q[u][i].l]=dis[u]+dis[v]-2*dis[find(v)];
        }
    }
}
int main()
{
    int t;
    int n,m,k;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        init(n);
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            //双向边
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=0;i<k;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            adde(a,b,i);
            adde(b,a,i);
        }
        num=0;//标记是第几棵树
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                num++;//标记是第棵树
                tarjan(i,0);//传递根和深度
            }
        }
        for(int i=0;i<k;i++)
        {
            if(ans[i]==-1)
            printf("Not connected\n");
            //cout<<"Not connected\n";
            else
            //cout<<ans[i]<<endl;
            printf("%d\n",ans[i]);
        }
    }
	return 0;
}

 1001 八数码问题

【上篇】
【下篇】

抱歉!评论已关闭.