P9705 「TFOI R1」Unknown Graph题解

思路

题目给出了每个点的初度和入度,由于图是无重边无自环的有向图

要求满足限制的图有多少条边与建图方案。

我们可以考虑使用网络流中的最大流

我们知道这是一道网络流后,题目的难点就转移到网络流的建模以及输出方案的办法。

网络流的建模:

题目所给的条件没有源点汇点并指出图可以不连通,所以考虑设立一个超级源点 $S$ 以及汇点 $T$。

由于有入度和初度的要求,图是有向图

考虑拆点

将每一个点拆成两个点,一个代表由其他点连向自己,即入度。

反之同理,另一个点代表自己连向其他点,即出度。

针对由有些边不能连,由于本题数据较小,我们可以使用一个邻接矩阵来存该边可不可以连。

注意:没有自环,所以自己不能连自己。

所以综上所述,本题的建模规则:

1:每个点的入度连向汇点流量为入度 $a _ {i}$

2:源点连向每个点的出度流量为出度 $b _ {i}$

3:可以连的边,起点的出度点连向终点的入读点流量为 1没有重边)。

保证有解,所以建模规则 1 和 2 中的连的边满流。

然后就可以跑一遍最大流 Dinic 板子。

输出方案:

连的边必然是建模规则 3 中连的边,当该边被选中时必然满流(因为流量只有 1)。

但这种边一定是由出度点连向入读点。

下面给出代码:

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 1e9
using namespace std;
int n,m,s,t;
int mapp[1005][1005]; 

int cnt=-1;
struct N{
    int from,to,next,val;
};N p[10000005];
int head[8005],cur[8005],d[8005];

inline void add(int a,int b,int c)
{
    ++cnt;
    p[cnt].from=a;
    p[cnt].next=head[a];
    head[a]=cnt;
    p[cnt].to=b;
    p[cnt].val=c;
    return ;
}

inline int bfs()
{
    queue<int> q;
    q.push(s);
    memset(d,-1,sizeof(d));
    d[s]=0;
    while(q.size()>0)
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=p[i].next)
        {
            int v=p[i].to;
            if(d[v]==-1&&p[i].val>0)
            {
                d[v]=d[u]+1;
                q.push(v);
            }
        }
    }
    if(d[t]==-1) return 0;
    else return 1;
}

inline int dfs(int u,int limit)
{
    if(u==t||!limit) return limit;
    int sum=0,flow=0;
    for(int i=cur[u];i!=-1;i=p[i].next)
    {
        int v=p[i].to;
        cur[u]=i;
        if(d[v]==d[u]+1&&p[i].val>0)
        {
            sum=dfs(v,min(p[i].val,limit));
            p[i].val-=sum;
            p[i^1].val+=sum;
            flow+=sum;
            limit-=sum;
            if(!limit) break;
        }
    }
    if(!flow) d[u]=-1;
    return flow;
}

inline void dinic()
{
    int ans=0;
    while(bfs())
    {
        for(int i=0;i<=t;i++) cur[i]=head[i];
        ans+=dfs(s,inf);
    }
    cout<<ans<<endl;
    return ;
}//板子 

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    memset(head,-1,sizeof(head));//s=0,memset head数组为-1 
    cin>>n;
    s=0,t=2*n+1;
    int a,b;
    for(int i=1;i<=n;i++)
    {
        cin>>a;
        add(i+n,t,a),add(t,i+n,0);//建模规则1 
    }
    for(int i=1;i<=n;i++)
    {
        cin>>a;
        add(s,i,a),add(i,s,0);//建模规则2
    }
    //拆点:这里我们将出度点的编号设为1-n,入度点就是n+1-2*n,所以汇点t可设为2*n+1 
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>a>>b;
        mapp[a][b]=1;//邻接矩阵
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(mapp[i][j]==0&&i!=j)//当此边没有被限制且不是自己连向自己时 
            {
                add(i,n+j,1),add(n+j,i,0);//连边 
            }
        }
    }
    dinic();
    for(int i=4*n-1;i<=cnt;i++)//建图规则3中连的边cnt值从4*n-1到cnt 
    {
        if(i%2==0&&p[i].val==0)//是从出度连向入度的边且满流了(val值为0) 
        {
            cout<<p[i].from<<" "<<p[i].to-n<<endl;//输出时入度点要记得减去n 
        }
    }
    return 0;
} 
posted @ 2023-10-07 17:47  keep_of_silence  阅读(6)  评论(0编辑  收藏  举报  来源
/*
*/