最大流,二分法,拆点法(士兵移动 uva 12264)

给n个点的无权无向图(n<=100),每个点有一个非负数ai。若ai==0则此点归敌方所有,若ai>0则此点归你且上面有ai个属于你的士兵。保证至少有一个属于你的点与敌方的点相邻。你可以让你的每个士兵最多移动一次,每次可以待在原地或者去到相邻的属于你的领地,但每个点至少要留1各士兵,使得最薄弱的关口尽量坚固。关口是指与敌方点相邻的点,薄弱与坚固分别指兵少与兵多。


我参考了这篇博客上的一些讲解。http://www.voidcn.com/blog/a197p/article/p-4252874.html

思路:把每个点拆成两个点,一个入度,一个出度,入度向自己的和每个相邻的点的出度连一条边,容量是ai,每个点出度连一条边到汇点,容量为1,那些与敌人相邻的点再多连一条边到汇点,容量是二分的值,我们只需要二分这个值,跑一下网络流,如果满流,表示可以,否则不行。


本篇通过第二个样例讲解思路,下图是第二个样例的建图结果。

敌方的点不需要参与建图,因此图中没有6,7号点。

INF指无穷,mid指二分的中值。


Q:为何需要拆点?

A:我们希望通过拆点来实现每个士兵最多移动一次。下图中每个INF的边都是士兵移动的边,观察后可以发现如此建图士兵只能从入点移动到另一个点的出点,因此最多只能移动一次。(根据题意每次最远移动到相邻的点)

Q:为何出点需要连一条容量为1的边到汇点?

A:为了保证己方的点至少有1个士兵,观察下图 1出 与 2出 满流时说明1和2点至少1个人。

Q:为何要二分?

A:我们要让最薄弱的关口尽量坚固,就得使关口的士兵分配得尽量均匀,然而最大流的算法不能保证分配均匀,我们只好一个个的试,满载就放宽条件,不满载就严加条件,直到试出可行的最大解。

以下渣代码

#include<stdio.h>
#include<vector>
#include<queue>
#include<string.h>
#define maxn 250
#define INF 0X3F3F3F3F
using namespace std;

int N;
int A[maxn];
char MAP[maxn][maxn];
bool ib[maxn];

struct Edge
{
    int from,to,cap,flow;
    Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};


struct EdmondsKarp
{
    int n,m;
    vector<Edge>edges;
    vector<int>G[maxn];
    int a[maxn];
    int p[maxn];

    void init(int n)
    {
        this->n=n;
        for(int i=0;i<n;i++) G[i].clear();
        edges.clear();
    }

    void AddEdge(int from,int to,int cap)
    {
        edges.push_back(Edge(from,to,cap,0));
        edges.push_back(Edge(to,from,0,0));
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    int Maxflow(int s,int t)
    {
        int flow=0;
        for(;;)
        {
            memset(a,0,sizeof(a));
            queue<int>Q;
            Q.push(s);
            a[s]=INF;
            while(!Q.empty())
            {
                int x=Q.front();Q.pop();
                for(unsigned int i=0;i<G[x].size();i++)
                {
                    Edge& e=edges[G[x][i]];
                    if(!a[e.to]&&e.cap>e.flow)
                    {
                        p[e.to]=G[x][i];
                        a[e.to]=min(a[x],e.cap-e.flow);
                        Q.push(e.to);
                    }
                }
                if(a[t]) break;
            }
            if(!a[t]) break;
            for(int u=t;u!=s;u=edges[p[u]].from)
            {
                edges[p[u]].flow+=a[t];
                edges[p[u]^1].flow-=a[t];
            }
            flow+=a[t];
        }
        return flow;
    }

    int Build(int val)
    {
        int ans=0;
        memset(ib,0,sizeof(ib));
        init(2*N+2);
        for(int i=1;i<=N;i++)
        {
            if(!A[i]) continue;
            AddEdge(0,i,A[i]);
            AddEdge(i,i+N,A[i]);
            for(int j=1;j<=N;j++)
                if(MAP[i][j]=='Y')
                {
                    if(!A[j]) ib[i]=true;
                    else AddEdge(i,j+N,INF);
                }
        }
        for(int i=1;i<=N;i++)
            if(ib[i]) {AddEdge(i+N,n-1,val);ans+=val;}
            else if(A[i]){AddEdge(i+N,n-1,1);ans++;};
        return ans;
    }

    void solve()
    {
        int a,b,ans;
        int l=0,r=10010;
        while(l<r)
        {
            int mid=(l+r)>>1;
            a=Build(mid);
            b=Maxflow(0,n-1);
            if(a==b)
            {
                l=mid+1;
                ans=mid;
            }
            else r=mid;
        }
        printf("%d\n",ans);
    }
};

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&N);
        for(int i=1;i<=N;i++) scanf("%d",&A[i]);
        for(int i=1;i<=N;i++) scanf("%s",MAP[i]+1);
        EdmondsKarp EK;
        EK.solve();
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值