spoj287 经典网络流题目,二分+网络流判定方案

题目大意:有n个房子,m条边。编号1的点是网络中心。现在给你k个房子,每个房子都要由一根网线连往网络中心。连线有两个规则:1、房子连到网络中心的一根网线必须完整,只有一个颜色。2、边上的所有网线必须颜色都不一样,边是两个房子之间的边,比如3->2->1就经过了两个边求:最少用多少个颜色。具体看题目。

思路:

肯定是最大流没错,关键还是建图。

网上题解一句话说“颜色的数量取决于网络流中各条边中最大的流量,于是二分边的容量并做网络流就可以了."感觉有点牵强。

首先如果你给每个边的颜色种数是无穷的话,那么你是会得到一个所有边中最大的流量,但是这个流量明显不是我们想要的答案,比如说源点一共有七个流,我们可以从s->1->2->t,或者s->3->4->t,那么就有可能是第一条路过了七个流而第二条路一个没过,其实最少的颜色种数肯定比7小,所以最大流算法只是说有种流法能够保证得到最大流,而不能控制流量具体往哪里流,情况颇多,当然最少颜色种数肯定是某个边上的最大流量。

但我们要做的是压榨每条边上的容量,迫使流改道,想想的话应该是让流更充分的使用容量的情况下更优,那么我们二分答案cap,让每个边的容量为cap,看在这个容量下是不是存在一个方案满足题意,如果有说明这个颜色种数够用,那么我们往下二分,找更小的颜色数;如果不存在即最大流小于k,那么说明颜色数不够,那么就往上调,最后得到一个最少的颜色数。

之前自己的dinic不好用,tle到死,可能是哪里没写好?只好去大牛博客上扒了一份isap的模板,挺好用的,征用了o(∩_∩)o 

#include <stdio.h>
#include <string.h>
#include<iostream>
#include <algorithm>
#define clear(A, X) memset (A, X, sizeof A)
#define copy(A, B) memcpy (A, B, sizeof A)
using namespace std;
const int maxE=1000000;
const int maxN=100000;
const int maxQ=1000000;
const int oo=0x3f3f3f3f;
struct Edge
{
int v;//弧尾
int c;//容量
int n;//指向下一条从同一个弧头出发的弧
} edge[maxE];//边组
int adj[maxN], cntE;//前向星的表头
int Q[maxQ], head, tail;//队列
int d[maxN], cur[maxN], pre[maxN], num[maxN];
int sourse, sink, nv;//sourse:源点,sink:汇点,nv:编号修改的上限
int n, m,k;
int x[maxN],f[maxN],g[maxN];

void addedge (int u, int v, int c)//添加边
{
//正向边
    edge[cntE].v = v;
    edge[cntE].c = c;//正向弧的容量为c
    edge[cntE].n = adj[u];
    adj[u] = cntE++;
//反向边
    edge[cntE].v = u;
    edge[cntE].c = 0;//反向弧的容量为0
    edge[cntE].n = adj[v];
    adj[v] = cntE++;
}
void rev_bfs ()  //反向BFS标号
{
    clear (num, 0);
    clear (d, -1);//没标过号则为-1
    d[sink] = 0;//汇点默认为标过号
    num[0] = 1;
    head = tail = 0;
    Q[tail++] = sink;
    while (head != tail)
    {
        int u = Q[head++];
        for (int i = adj[u]; ~i; i = edge[i].n)
        {
            int v = edge[i].v;
            if (~d[v]) continue;//已经标过号
            d[v] = d[u] + 1;//标号
            Q[tail++] = v;
            num[d[v]]++;
        }
    }
}
int ISAP()
{
    copy (cur, adj);//复制,当前弧优化
    rev_bfs ();//只用标号一次就够了,重标号在ISAP主函数中进行就行了
    int flow = 0, u = pre[sourse] = sourse, i;
    while (d[sink] < nv)  //最长也就是一条链,其中最大的标号只会是nv - 1,如果大于等于nv了说明中间已经断层了.
    {
        if (u == sink)  //如果已经找到了一条增广路,则沿着增广路修改流量
        {
            int f = oo, neck;
            for (i = sourse; i != sink; i = edge[cur[i]].v)
            {
                if (f > edge[cur[i]].c)
                {
                    f = edge[cur[i]].c;//不断更新需要减少的流量
                    neck = i;//记录回退点,目的是为了不用再回到起点重新找
                }
            }
            for (i = sourse; i != sink; i = edge[cur[i]].v)  //修改流量
            {
                edge[cur[i]].c -= f;
                edge[cur[i] ^ 1].c += f;
            }
            flow += f;//更新
            u = neck;//回退
        }
        for (i = cur[u]; ~i; i = edge[i].n) if (d[edge[i].v] + 1 == d[u] && edge[i].c) break;
        if (~i)  //如果存在可行增广路,更新
        {
            cur[u] = i;//修改当前弧
            pre[edge[i].v] = u;
            u = edge[i].v;
        }
        else  //否则回退,重新找增广路
        {
            if (0 == (--num[d[u]])) break;//GAP间隙优化,如果出现断层,可以知道一定不会再有增广路了
            int mind = nv;
            for (i = adj[u]; ~i; i = edge[i].n)
            {
                if (edge[i].c && mind > d[edge[i].v])  //寻找可以增广的最小标号
                {
                    cur[u] = i;//修改当前弧
                    mind = d[edge[i].v];
                }
            }
            d[u] = mind + 1;
            num[d[u]]++;
            u = pre[u];//回退
        }
    }
    return flow;
}
void init ()  //初始
{
clear (adj, -1);
cntE = 0;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&k);
        sourse=0,sink=1;
        nv=n+1;
        for(int i=1; i<=k; i++)
        {
            scanf("%d",&x[i]);
        }
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d",&f[i],&g[i]);
        }
        int  l=0,r=n;
        int answer=0;
        while(l<=r)
        {
            init();
            for(int i=1; i<=k; i++)
            {
                addedge(0,x[i],1);//从0开始到2*k-1
            }
            int mid=(l+r)>>1;
            for(int i=1; i<=m; i++)
            {
                addedge(f[i],g[i],mid);
                addedge(g[i],f[i],mid);
            }
            if(ISAP()==k)
            {
//                printf("--------%d\n",mid-1);
                answer=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("%d\n",answer);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值