网络流阶段性总结

网络流,一种建图的艺术

显然我没有那种艺术细胞(悲

$\ $

最大流

dinic+当前弧优化

$\ $

P1231
#include
using namespace std;
const int N=4e4+10,M=1e5+10,inf=1e9;
int n1,n2,n3,m1,m2,s,t;
int cur[N],vis[N],dis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
bool bfs(){
    for(int i=1;i<=t;i++)dis[i]=0,cur[i]=head[i];
    queueq;
    dis[s]=1;
    q.push(s);
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(!dis[y]&&edge[i]){
                dis[y]=dis[x]+1;
                q.push(y);
            }
        }
    }
    return (dis[t]!=0);
}
int dinic(int x,int limit){
    if(x==t||!limit)return limit;
    int flow=0,f;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(dis[y]==dis[x]+1&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            if(!limit)break;
        }
    }
    return flow;
}
int main()
{
    scanf("%d%d%d",&n1,&n2,&n3);
    s=n1+n1+n2+n3+1,t=n1+n1+n2+n3+2;
    scanf("%d",&m1);
    for(int i=1,x,y;i<=m1;i++){
        scanf("%d%d",&x,&y);
        add(x+n2,y,0),add(y,x+n2,1);
    }
    scanf("%d",&m2);
    for(int i=1,x,y;i<=m2;i++){
        scanf("%d%d",&x,&y);
        add(x+n2+n1,y+n1+n2+n1,1),add(y+n1+n2+n1,x+n2+n1,0);
    }
    for(int i=1;i<=n1;i++){
        add(i+n2,i+n2+n1,1),add(i+n2+n1,i+n2,0);
    }
    for(int i=1;i<=n2;i++){
        add(s,i,1),add(i,s,0);
    }
    for(int i=1;i<=n3;i++){
        add(i+n1+n2+n1,t,1),add(t,i+n1+n2+n1,0);
    }
    int flow=0;
    while(bfs()){
        flow+=dinic(s,inf);
    }
    printf("%d\n",flow);
    return 0;
}

$\ $

  • P1345 [USACO5.4] 奶牛的电信Telecowmunication
    拆成出边点和入边点,控制点的流量为 \(1\)
    路径会通过相连的信息建立联系,路径上的每个点都先走到入点,经过流量为 \(1\) 的控制边,再走到出点,保证了流量合法
P1345
#include
using namespace std;
const int N=410,M=2010,inf=1e9;
int n,m,c1,c2;
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
int cur[N],d[N];
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
bool bfs(){
    for(int i=1;i<=n*2;i++)cur[i]=head[i],d[i]=0;
    queueq;
    d[c1]=1;
    q.push(c1);
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(!d[y]&&edge[i]){
                d[y]=d[x]+1;
                q.push(y);
            }
        }
    }
    return (d[c2]!=0);
}
int dinic(int x,int limit){
    if(x==c2||!limit)return limit;
    int flow=0,f;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            if(!limit)break;
        }
    }
    return flow;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&c1,&c2);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        add(x+n,y,inf),add(y,x+n,0);
        add(y+n,x,inf),add(x,y+n,0);
    }
    for(int i=1;i<=n;i++){
        if(i!=c1&&i!=c2)add(i,i+n,1),add(i+n,i,0);
        else add(i,i+n,inf),add(i+n,i,0);
    }
    int flow=0;
    while(bfs()){
        flow+=dinic(c1,inf);
    }
    printf("%d\n",flow);
    return 0;
}

$\ $

最小割

最后的网络中,与S相连的相当于选进 \(S\) 集合,与 \(T\) 相连的则是选进 \(T\) 集合
最小割是分成两个集合的最小割边代价

$\ $

  • P1361 小M的作物
    把每个植物组合作为两个新点。\(S\) 向新点\(1\)连流量 \(c_{i,1}\) 的边,新点 \(2\)\(T\) 连流量 \(c_{i,2}\) 的边,表示不选进这两个集合的话分别少收获多少贡献。
    新点 \(1\) 向相应的作物点连不会被割掉的流量为 \(inf\) 的边,同样地,作物向新点 \(2\) 连不会被割掉的边。
    如果某个组合连向 \(S\) 的边没有被割掉,那么组合中所有作物连向 \(T\) 的点都被割掉了。反之同理。
P1361
#include
#define ll long long
using namespace std;
const int N=1e5+10,M=3e6+10;
const ll inf=1e18;
int n,s,t,m,cnt,cur[N],d[N];
ll a[N],b[N],edge[M],sum;
int ver[M],head[N],tot=1,Next[M];
void add(int x,int y,ll z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
bool bfs(){
    for(int i=1;i<=cnt;i++)d[i]=0,cur[i]=head[i];
    d[s]=1;
    queueq;
    q.push(s);
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(!d[y]&&edge[i]){
                d[y]=d[x]+1;
                q.push(y);
            }
        }
    }
    return (d[t]!=0);
}
ll dinic(int x,ll limit){
    if(x==t||!limit)return limit;
    ll flow=0,f;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            if(!limit)break;
        }
    }
    return flow;
}
int main()
{
    scanf("%d",&n);
    s=n+1,t=cnt=n+2;
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        add(s,i,a[i]),add(i,s,0);
        sum+=a[i];
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&b[i]);
        add(i,t,b[i]),add(t,i,0);
        sum+=b[i];
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int k,c1,c2;
        scanf("%d%d%d",&k,&c1,&c2);
        sum+=c1+c2;
        add(s,cnt+1,c1),add(cnt+1,s,0);
        add(cnt+2,t,c2),add(t,cnt+2,0);
        for(int j=1,x;j<=k;j++){
            scanf("%d",&x);
            add(cnt+1,x,inf),add(x,cnt+1,0);
            add(x,cnt+2,inf),add(cnt+2,x,0);
        }
        cnt+=2;
    }
    ll flow=0;
    while(bfs()){
        flow+=dinic(s,inf);
    }
    printf("%lld\n",sum-flow);
    return 0;
}
$\ $
  • P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
    最小割连边的板子模型之一
    \(S\)\(0\),\(T\)\(1\),每个人向自己的意愿连边,朋友之间连流量为 \(1\) 的边。
    要么割意愿边,满足朋友投了同一种票;要么割朋友边,满足自己的意愿但发生冲突。
P2057
#include
using namespace std;
const int N=2010,M=2e5+10,inf=1e9;
int n,m,s,t,ans,cnt,cur[N],d[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
    ver[++tot]=x;
    Next[tot]=head[y];
    head[y]=tot;
    edge[tot]=z;
}
bool bfs(){
    for(int i=1;i<=cnt;i++)cur[i]=head[i],d[i]=0;
    d[s]=1;
    queueq;
    q.push(s);
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(!d[y]&&edge[i]){
                d[y]=d[x]+1;
                q.push(y);
            }
        }
    }
    return (d[t]!=0);
}
int dinic(int x,int limit){
    if(x==t||!limit)return limit;
    int flow=0,f;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            if(!limit)break;
        }
    }
    return flow;
}
int main()
{
    scanf("%d%d",&n,&m);
    s=n+1,t=cnt=n+2;
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        if(x==1)add(i,t,1);
        else add(s,i,1);
    }
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        add(x,y,1),add(y,x,1);
    }
    int flow=0;
    while(bfs()){
        flow+=dinic(s,inf);
    }
    printf("%d\n",flow);
    return 0;
}
$\ $
  • P2762 太空飞行计划问题
    源点向实验连边,实验向对应仪器连边,仪器向汇点连边,初始统计所有实验的价值
    割掉实验表示不选这场实验,割掉仪器表示付出仪器的代价完成实验
P2762
#include
#define ll long long
using namespace std;
const int N=2e5+10;
const ll inf=1e18;
int m,n,c[N],s,t,d[N],cur[N],p[N],a[N],cnt,vis[N];
int ver[N<<1],head[N],tot=1,Next[N<<1];
ll edge[N<<1],ans;
void add(int x,int y,ll z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
    ver[++tot]=x;
    Next[tot]=head[y];
    head[y]=tot;
    edge[tot]=0;
}
bool bfs(){
    for(int i=1;i<=t;i++)cur[i]=head[i],d[i]=0;
    d[s]=1;
    queueq;
    q.push(s);
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(!d[y]&&edge[i]){
                d[y]=d[x]+1;
                q.push(y);
            }
        }
    }
    return (d[t]!=0);
}
ll dinic(int x,ll limit){
    if(x==t||!limit)return limit;
    ll flow=0,f;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            if(!limit)break;
        }
    }
    return flow;
}
int main()
{
    scanf("%d%d",&m,&n);
    s=n+m+1,t=n+m+2;
    for(int i=1;i<=m;i++){
        scanf("%d",&c[i]);
        ans+=c[i];
        add(s,i,c[i]);
        char tools[10000];
        memset(tools,0,sizeof tools);
        cin.getline(tools,10000);
        int ulen=0,tool;
        while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用
        {//tool是该实验所需仪器的其中一个      
            //这一行,你可以将读进来的编号进行储存、处理,如连边。
            add(i,m+tool,inf);
            if (tool==0) 
                ulen++;
            else {
                while (tool) {
                    tool/=10;
                    ulen++;
                }
            }
            ulen++;
        }
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&p[i]);
        add(i+m,t,p[i]);
  //      printf("i:%d p:%d\n",i,p[i]);
    }
    ll flow=0;
    while(bfs()){
        flow+=dinic(s,inf);
    }
    for(int i=1;i<=m;i++)if(d[i])printf("%d ",i);
    printf("\n");
    for(int i=1;i<=n;i++)if(d[m+i])printf("%d ",i);
    printf("\n%lld\n",ans-flow);
    return 0;
}

$\ $

费用流

用SPFA代替bfs的部分,\(d[y]=d[x]+1\)改成\(d[y]=(d[x]+cost[i])_{min}\)
因为有负边权,所以在 \(dinic\) 里也要注意用 \(vis\) 来标记该点是否在栈中

$\ $

P3381
#include
using namespace std;
const int N=5e3+10,M=5e4+10,inf=2147483647;
int n,m,s,t,ans,cur[N],d[N],vis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1],cost[M<<1];
void add(int x,int y,int z,int c){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
    cost[tot]=c;
    ver[++tot]=x;
    Next[tot]=head[y];
    head[y]=tot;
    edge[tot]=0;
    cost[tot]=-c;
}
bool bfs(){
    for(int i=1;i<=n;i++)cur[i]=head[i],d[i]=inf,vis[i]=0;
    queueq;
    d[s]=1;
    q.push(s);
    while(q.size()){
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(d[y]>d[x]+cost[i]&&edge[i]){
                d[y]=d[x]+cost[i];
                if(!vis[y]){
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
    return (d[t]!=inf);
}
int dinic(int x,int limit){
    if(x==t||!limit)return limit;
    int flow=0,f;
    vis[x]=1;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(!vis[y]&&d[y]==d[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            ans+=f*cost[i];
            if(!limit)break;
        }
    }
    vis[x]=0;
    return flow;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1,x,y,z,c;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&z,&c);
        add(x,y,z,c);
    }
    int flow=0;
    while(bfs()){
        flow+=dinic(s,inf);
    }
    printf("%d %d\n",flow,ans);
    return 0;
}

$\ $

  • P4452 [国家集训队] 航班安排
    每个请求只能执行一次,所以把请求拆点,控制流量为 \(1\)。如果从0机场到一个请求起始机场的时间和从这个请求结束机场到 \(0\) 机场的时间都在 \(0-t\) 的时间范围内,则把该请求的点纳入建图。
    同时,很多请求可能互相不冲突,执行完一个请求以后可以去执行另一个。因此对于不同请求之间互相进行类似的判断并连边。
    把收入和花费都取相反数,跑最小费用最大流,最后答案取反得最大收入。
P4452
#include
#define ll long long
using namespace std;
const int N=2010,M=1e6+10;
const ll inf=1e18;
ll ans,dis[N],edge[M],cost[M];
int cur[N],vis[N];
int ver[M],tot=1,head[N],Next[M];
int n,m,k,t,T[N][N],F[N][N],cnt,S,E,s0;
int S0[N],T0[N],A[N],B[N],C[N],tag[N],fir[N],en[N];
void add(int x,int y,ll z,ll c){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
    cost[tot]=c;
    ver[++tot]=x;
    Next[tot]=head[y];
    head[y]=tot;
    edge[tot]=0;
    cost[tot]=-c;
}
bool bfs(){
    for(int i=1;i<=cnt;i++)cur[i]=head[i],vis[i]=0,dis[i]=inf;
    dis[S]=0;
    queueq;
    q.push(S);
    while(q.size()){
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(dis[y]>dis[x]+cost[i]&&edge[i]){
                dis[y]=dis[x]+cost[i];
                if(!vis[y]){
					vis[y]=1;
	                q.push(y);
				}
            }
        }
    }
    return (dis[E]!=inf);
}
ll dinic(int x,ll limit){
    if(!limit||x==E)return limit;
    ll flow=0,f;
    vis[x]=1;
    for(int &i=cur[x];i;i=Next[i]){
        int y=ver[i];
        if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,1ll*edge[i])))){
            flow+=f,limit-=f;
            edge[i]-=f,edge[i^1]+=f;
            ans+=cost[i]*f;
            if(!limit)break;
        }
    }
    vis[x]=0;
    return flow;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&k,&t);
    S=1,E=2,cnt=s0=3;
    add(S,s0,k,0);
    for(int i=0;it||T[0][A[i]]>S0[i])continue;
        tag[i]=1;
        add(s0,cnt+1,inf,F[0][A[i]]);
        add(cnt+1,cnt+2,1,-C[i]);
        add(cnt+2,E,inf,F[B[i]][0]);
        fir[i]=cnt+1,en[i]=cnt+2;
        cnt+=2;
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=m;j++){
            if(i==j||!tag[i]||!tag[j])continue;
            if(T0[i]+T[B[i]][A[j]]<=S0[j])add(en[i],fir[j],inf,F[B[i]][A[j]]);
        }
    }
    ll flow=0;
    while(bfs()){
        flow+=dinic(S,inf);
    }
    printf("%lld\n",-ans);
    return 0;
}

$\ $

  • P2153 [SDOI2009] 晨跑
    每个点都只能走一次,所以拆点,然后跑最小费用最大流
    注意如果有 \(1→n\) 的边,这条边只能走一次。
P2153
#include
#define ll long long
using namespace std;
const int N=510,M=4e4+10,inf=1e9+10;
const ll Inf=1e18;
int n,m,s,t,cnt;
int cur[N],vis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
ll cost[M<<1],ans,dis[N];
void add(int x,int y,int z,int c){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
	edge[tot]=z;
	cost[tot]=c;
	ver[++tot]=x;
	Next[tot]=head[y];
	head[y]=tot;
	edge[tot]=0;
	cost[tot]=-c;	
}
bool bfs(){
	for(int i=1;i<=n*2;i++)dis[i]=Inf,vis[i]=0,cur[i]=head[i];
	queueq;
	q.push(1);
	dis[1]=0;
	while(q.size()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=Next[i]){
			int y=ver[i];
			if(dis[y]>dis[x]+cost[i]&&edge[i]){
				dis[y]=dis[x]+cost[i];
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return (dis[n]!=Inf);
}
int dinic(int x,int limit){
	if(x==n||!limit)return limit;
	int flow=0,f;
	vis[x]=1;
	for(int &i=cur[x];i;i=Next[i]){
		int y=ver[i];
		if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
			flow+=f,limit-=f;
			edge[i]-=f,edge[i^1]+=f;
			ans+=f*cost[i];
			if(!limit)break;
		}
	}
	vis[x]=0;
	return flow;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)add(i,i+n,(i==1||i==n)?inf:1,0);
	for(int i=1,a,b,c;i<=m;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a+n,b,(a==1&&b==n)?1:inf,c);
	}
	int flow=0;
	while(bfs()){
		flow+=dinic(1,inf);
	}
	printf("%d %lld\n",flow,ans);
	return 0;
}

$\ $

  • P2053 [SCOI2007] 修车
    发现在某个技术人员这里,倒数第一辆被修的车会有一个人在等,倒数第二辆被修的车会有两个人在等……倒数第 \(k\) 辆被修的车对应 \(k*t_{i,j}\)的等待时间。同时,每个技术人员修的车中,只会有一辆是倒数第一辆,只会有一辆是倒数第二辆……
    把每个技术人员拆成 \(n\) 个点,表示修倒数第 \(k\) 辆车,同时控制点的流量为 \(1\)(同样是拆点),每个出点向汇点连边。每辆车分别向每个技术人员的 \(k\) 个点连边,表示这辆车 \(i\) 被某个技术人员 \(j\) 作为倒数第 \(k\) 辆修,花费就是等待时间 \(k*t_{i,j}\)
    跑最小费用最大流,最后的答案 \(/n\) 就是平均等待时间。
    对于这种同一时间只能处理一个事项,需要排队,并且处理每个事项所占的时间/空间是独一无二的,感觉比较常用这种每个处理器拆成目标个数个点的处理方法。
P2053
#include
#define ll long long
using namespace std;
const int N=110,inf=1e9+10; 
const ll Inf=1e18;
int n,m,s,t,fir[N][N],lst[N][N],cnt,cur[N*N];
ll ans,cost[N*N*N],dis[N*N],vis[N*N];
int ver[N*N*N],head[N*N],tot=1,Next[N*N*N],edge[N*N*N];
void add(int x,int y,int z,int c){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
	edge[tot]=z;
	cost[tot]=c;
	ver[++tot]=x;
	Next[tot]=head[y];
	head[y]=tot;
	edge[tot]=0;
	cost[tot]=-c;
}
bool bfs(){
	for(int i=1;i<=cnt;i++)vis[i]=0,cur[i]=head[i],dis[i]=Inf;
	dis[s]=0;
	queueq;
	q.push(s);
	while(q.size()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=Next[i]){
			int y=ver[i];
			if(dis[y]>dis[x]+cost[i]&&edge[i]){
				dis[y]=dis[x]+cost[i];
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return (dis[t]!=Inf);
}
int dinic(int x,int limit){
	if(x==t||!limit)return limit;
	int flow=0,f;
	vis[x]=1;
	for(int &i=cur[x];i;i=Next[i]){
		int y=ver[i];
		if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
			flow+=f,limit-=f;
			edge[i]-=f,edge[i^1]+=f;
			ans+=f*cost[i];
			if(!limit)break;
		}
	}
	vis[x]=0;
	return flow;
}
int main()
{
	scanf("%d%d",&m,&n);
	s=n+1,t=cnt=n+2;
	for(int i=1;i<=n;i++)add(s,i,1,0);
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			fir[i][j]=++cnt,lst[i][j]=++cnt;
			add(fir[i][j],lst[i][j],1,0);
			add(lst[i][j],t,inf,0);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1,x;j<=m;j++){
			scanf("%d",&x);
			for(int k=1;k<=n;k++){
				add(i,fir[j][k],inf,x*k);
			}
		}
	}
	while(bfs())dinic(s,inf);
	printf("%.2lf\n",(double)ans/n);
	return 0;
}

$\ $

  • P2517 [HAOI2010] 订货
    为数不多100%自己搞出来的题,虽然是个板子TvT
    每个月都能订货且费用不同,所以从源点向每个月的点连流量 \(INF\),费用为订货单价的边。每个月有库存容量和单位储存花费,那么每个月向下一个月连流量 \(S\),费用为单位储存花费的边。最后每个月有个需求量,那么每个月的点向汇点连流量为需求量的边。
P2517
#include
#define ll long long
using namespace std;
const int N = 210, M = 5010, inf = 1e9 + 10;
const ll INF = 1e16;
int n, m, S, x, s, t;
int cur[N], vis[N];
int ver[M << 1], head[N], tot = 1, Next[M << 1], edge[M << 1];
ll cost[M << 1], dis[M << 1], ans;
void add(int x, int y, int z, int c) {
	ver[++tot] = y;
	Next[tot] = head[x];
	head[x] = tot;
	edge[tot] = z;
	cost[tot] = c;
	ver[++tot] = x;
	Next[tot] = head[y];
	head[y] = tot;
	edge[tot] = 0;
	cost[tot] = -c;
}
bool bfs() {
	for (int i = s;i <= t;i++)cur[i] = head[i], dis[i] = INF, vis[i] = 0;
	dis[s] = 0;
	queueq;
	q.push(s);
	while (q.size()) {
		int x = q.front();
		q.pop();
		vis[x] = 0;
		for (int i = head[x];i;i = Next[i]) {
			int y = ver[i];
			if (edge[i] && dis[y] > dis[x] + cost[i]) {
				dis[y] = dis[x] + cost[i];
				if (!vis[y]) {
					vis[y] = 1;
					q.push(y);
				}
			}
		}
	}
	return (dis[t] != INF);
}
int dinic(int x, int limit) {
	if (x == t || !limit)return limit;
	int flow = 0, f;
	vis[x] = 1;
	for (int& i = cur[x];i;i = Next[i]) {
		int y = ver[i];
		if (!vis[y] && dis[y] == dis[x] + cost[i] && (f = dinic(y, min(limit, edge[i])))) {
			flow += f, limit -= f;
			edge[i] -= f, edge[i ^ 1] += f;
			ans += f * cost[i];
			if (!limit)break;
		}
	}
	vis[x] = 0;
	return flow;
}
int main()
{
	scanf("%d%d%d", &n, &m, &S);
	s = 0, t = n + 1;
	for (int i = 1;i <= n;i++) {
		scanf("%d", &x);
		if (i < n)add(i, i + 1, S, m);
		add(i, t, x, 0);
	}
	for (int i = 1;i <= n;i++) {
		scanf("%d", &x);
		add(s, i, inf, x);
	}
	while (bfs())dinic(s, inf);
	printf("%lld\n", ans);
	return 0;
}

$\ $

  • P2050 [NOI2012] 美食节
    和修车那题相似,也是一道涉及排队和处理任务的题。不过这题如果直接把所有点都建出来会达到恐怖的点数,因此在过程中动态加点。
    如果在某遍 \(dinic\) 之后,某个厨师拆出来最靠后的点满流,那么对于这个厨师再拆出一个等待时间 \(+1\) 份的新点。\((k→k+1)\)
P2517
#include
#define ll long long
using namespace std;
const ll INF=1e18;
const int N=4e5+10,M=2e6+10,inf=1e9;
ll cost[M<<1],ans,dis[N];
int cur[N],vis[N],pos[N],tim[210][210],siz[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
int n,m,s,t,num,cnt;
void add(int x,int y,int z,int c){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
	edge[tot]=z;
	cost[tot]=c;
	ver[++tot]=x;
	Next[tot]=head[y];
	head[y]=tot;
	edge[tot]=0;
	cost[tot]=-c;
}
bool bfs(){
	for(int i=1;i<=cnt;i++)dis[i]=INF,vis[i]=0,cur[i]=head[i];
	dis[s]=0;
	queueq;
	q.push(s);
	while(q.size()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=Next[i]){
			int y=ver[i];
			if(dis[y]>dis[x]+cost[i]&&edge[i]){
				dis[y]=dis[x]+cost[i];
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return (dis[t]!=INF);
}
int dinic(int x,int limit){
	if(x==t||!limit)return limit;
	int flow=0,f;
	vis[x]=1;
	for(int &i=cur[x];i;i=Next[i]){
		int y=ver[i];
		if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
			flow+=f,limit-=f;
			edge[i]-=f,edge[i^1]+=f;
			ans+=f*cost[i];
			if(!limit)break;
		}
	}
	vis[x]=0;
	return flow;
}
int main()
{
	scanf("%d%d",&n,&m);
	s=n+1,t=cnt=n+2;
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		num+=x;
		add(s,i,x,0);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&tim[i][j]);
		}
	}
	for(int i=1;i<=m;i++){
		add(cnt+1,cnt+2,1,0);
		pos[i]=tot-1;
		add(cnt+2,t,inf,0);		
		for(int j=1;j<=n;j++){
			add(j,cnt+1,inf,tim[j][i]);
		}
		siz[i]=1;
		cnt+=2;
	}
	while(bfs()){
		dinic(s,inf);
		for(int i=1;i<=m;i++){
			if(!edge[pos[i]]){
				add(cnt+1,cnt+2,1,0);
				pos[i]=tot-1;
				siz[i]++;
				add(cnt+2,t,inf,0);	
				for(int j=1;j<=n;j++){
					add(j,cnt+1,inf,tim[j][i]*siz[i]);		
				}
				cnt+=2;
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

继续学习ing……

posted @ 2023-07-16 19:54  Chloris_Black  阅读(29)  评论(0编辑  收藏  举报