ZOJ 3229 Shoot the Bullet(有源汇有上下界最大流)

Description
A要给m个人拍照片,第x个人至少要拍Gx张照片,整个拍摄过程要分n天进行,第k天要给给定的Ck个人拍,代号分别为Tk1, Tk2…..,TkCk,这一天给他们拍的照片数有限制,范围是[Lki,Rki],同时这一天也有拍照总数限制Dk。求满足所有要求的情况下,能拍的最多照片数。若无解则输出-1。
Input
多组用例,每组用例第一行为两个整数n和m表示拍摄天数和拍摄人数,第二行m个整数表示每个人要拍的照片数量下限Gi,然后输入每天拍摄的限制,首先是两个整数Ck和Dk表示第k天要给Ck个人拍照,最多拍Dk张,之后Ck行每行三个整数Tki,Lki,Rki表示给第Tki个人拍摄的照片在[Lki,Rki]之间,以文件尾结束输入
Output
对于每组用例,如果存在满足条件的方案则输出拍摄照片数最多的情况下输出总拍摄照片数以及每天给每个人拍摄的照片数,否则输出去-1
Sample Input
2 3
12 12 12
3 18
0 3 9
1 3 9
2 3 9
3 18
0 3 9
1 3 9
2 3 9

2 3
12 12 12
3 18
0 3 9
1 3 9
2 3 9
3 18
0 0 3
1 3 6
2 6 9

2 3
12 12 12
3 15
0 3 9
1 3 9
2 3 9
3 21
0 0 3
1 3 6
2 6 12
Sample Output
36
6
6
6
6
6
6

36
9
6
3
3
6
9

-1

Solution
设汇点ss=0,源点ee=n+m+1,超级源点s=n+m+2,超级汇点e=n+m+3,两排点,1~n表示n天,n+1到n+m表示m个人,从源点到1~n之间有上下限为[0,Di]的边,从n+1~n+m之间有上下限为[Gi,INF]的边,从1~n到n+1~n+m之间有上下限为[Lki,Rki]的边,再从汇点ee到源点ss连一条上下限为[0,INF]的边,那么图就变为一张无源汇有上下界的图,之后根据无源汇有上下界可行流的求解方案从各点到超级汇点或者从超级源点到各点之间建边,如果新图有可行流说明存在可行方案,没有可行流就没有可行方案,但是可行方案不一定是最优方案,所以还需要去掉超级源点和超级汇点以及汇点到源点的边之后在残余网络上再求一遍最大流,两边最大流的流量之和即为所能拍摄的最多照片数,而每条边的实际流量就是该边的下限加上求完第二次最大流后残余网络中该边对应的反向弧中流量
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define maxn 1555
#define maxm 222222
#define INF 10000000
int head[maxn],cur[maxn],d[maxn],st[maxm],f[maxn],l[maxn][maxn],id[maxn][maxn];
int s,e,ss,ee,no,n,m,sum;//s为源点,e为汇点,no为边数 
struct point
{
    int u,v,flow,next;
    point(){};
    point(int x,int y,int z,int w):u(x),v(y),next(z),flow(w){};
}p[maxm];
void add(int x,int y,int z)//从x到y建容量为z的边 
{
    p[no]=point(x,y,head[x],z);//前向弧,标号为偶 
    head[x]=no++;
    p[no]=point(y,x,head[y],0);//后向弧,标号为奇 
    head[y]=no++;
}
void init()//初始化 
{
    memset(id,0,sizeof(id));
    memset(f,0,sizeof(f));
    memset(head,-1,sizeof(head));
    no=sum=0;
}
bool bfs()
{
    int i,x,y;
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[s]=0; 
    q.push(s);
    while(!q.empty())
    {
        x=q.front();    
        q.pop();
        for(i=head[x];i!=-1;i=p[i].next)
        {
            if(p[i].flow&& d[y=p[i].v]<0)
            {
                d[y]=d[x]+1;
                if(y==e)    
                    return true;
                q.push(y);
            }
        }
    }
    return false;
}
int dinic()//最大流 
{
    int i,loc,top,x=s,nowflow,maxflow=0;
    while(bfs())
    {
        memcpy(cur,head,sizeof(head));
        top=0;
        while(true)
        {
            if(x==e)
            {
                nowflow=INF;
                for(i=0;i<top;i++)
                {
                    if(nowflow>p[st[i]].flow)
                    {
                        nowflow=p[st[i]].flow;
                        loc=i;
                    }
                }
                for(i=0;i<top;i++)
                {
                    p[st[i]].flow-=nowflow;
                    p[st[i]^1].flow+=nowflow;
                }
                maxflow+=nowflow;
                top=loc;    
                x=p[st[top]].u;
            }
            for(i=cur[x];i!=-1;i=p[i].next)
                if(p[i].flow&&d[p[i].v]==d[x]+1) 
                    break;
            cur[x]=i;
            if(i!=-1)
            {
                st[top++]=i;
                x=p[i].v;
            }
            else 
            {
                if(!top)    
                    break;
                d[x]=-1;
                x=p[st[--top]].u;
            }
        }
    }
    return maxflow;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        ss=0,ee=n+m+1;//源汇
        s=n+m+2,e=n+m+3;//超级源汇
        int G,C,D,T,L,R;
        for(int i=1;i<=m;i++)//从每个人到汇点有上下限为[G,INF]的边 
        {
            scanf("%d",&G);
            add(i+n,ee,INF-G);
            f[ee]+=G,f[i+n]-=G;
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&C,&D);
            add(ss,i,D);//从源点到每天有上下限为[0,D]的边 
            while(C--)
            {
                scanf("%d%d%d",&T,&L,&R);
                T++;
                add(i,T+n,R-L);//每天和每个人之间有上下限为[L,R]的边 
                f[T+n]+=L,f[i]-=L;
                l[i][T]=L;
                id[i][T]=no-1;
            }
        }
        for(int i=1;i<=n+m+1;i++)//各点与超级源汇建边 
            if(f[i]>0)sum+=f[i],add(s,i,f[i]);
            else add(i,e,-f[i]);
        int pos=no;//记录汇点到源点建的边的编号便于后面删边 
        add(ee,ss,INF);//汇点与源点有上下限为[0,INF]的边 
        int ans=dinic();//求无源汇有上下界可行流 
        if(ans!=sum)printf("-1\n");//可行流不存在 
        else
        {
            s=ss,e=ee,p[pos].flow=p[pos^1].flow=0;//删掉超级源汇以及汇点连向源点的边 
            ans+=dinic();//在残余网络上再跑一次最大流 
            printf("%d\n",ans);
            for(int i=1;i<=n;i++)
                for(int j=1;j<=m;j++)
                {
                    if(id[i][j])
                        printf("%d\n",p[id[i][j]].flow+l[i][j]);
                }
        }
        printf("\n");
    }
    return 0;
}

你可能感兴趣的:(ZOJ 3229 Shoot the Bullet(有源汇有上下界最大流))