牛客上大5278 L. 动物森友会| 二分 最大流

题目地址:https://ac.nowcoder.com/acm/contest/5278/L

思路

满足单调性质: 天数越多,跑出的最大流越大(即能完成的任务数量越多)
所以二分天数,在当前天数下建图,跑最大流;

建模如下:

一、四种点:

源点s、汇点t、周日期1~7、任务事件

源点编号:s = 0,
周编号:s+1,s+2....s+7,
任务编号:s+7+1,s+7+2...s+7+n,
汇点编号:t = s+7+n+1

二、一共有三种边

1.源点s->周1~周7 容量为mid/7*ee + (mid%7 >=i) * ee。(这一周日期下能做的任务总次数)
2.任务1~n->汇点t 容量为c[i]。(需要的完成的次数)
3.周->任务 容量为无穷大。(一天可以做无限次同一个任务)

代码

#include<bits/stdc++.h>
using namespace std;
const int inf = (1u<<31)-1, maxn = 1500, maxm = 100000;
struct edge { int to, nex, cap; } e[maxm];
int tot, head[maxn], dep[maxn],cur[maxn]; // 初始化tot=2, head[0..N]=-1

int c[maxn], m[maxn], g[maxn][10];
int sum = 0;
int n,ee;
int s = 0, t = 0;

//板子开始
void addedge(int u, int v, int cap, int rev = 0) {
    e[tot] = edge { v, head[u], cap }; head[u] = tot++;
    e[tot] = edge { u, head[v], rev }; head[v] = tot++;
}

bool bfs(int s, int t) {
	for(int i=0;i<=n+10;i++) cur[i]=head[i];
    static int Q[maxn]; int front = 0, rear = 0;
    memset(dep, 0, sizeof(dep)); Q[rear++] = s, dep[s] = 1;
    while (front != rear) {
        int u = Q[front++], v;
        for (int i = head[u]; ~i; i = e[i].nex) {
            if (e[i].cap > 0 && dep[v = e[i].to] == 0) {
                dep[v] = dep[u] + 1;
                if (v == t) return true;
                Q[rear++] = v;
            }
        }
    }
    return false;
}

int dfs(int u, int t, int f) {
    if (u == t) return f; int d, v, c = 0;
    for (int i = cur[u]; i!=-1; i = e[i].nex) {
    	cur[u] = i; //当前弧优化 
        if (e[i].cap > 0 && dep[u] + 1 == dep[v = e[i].to]) {
            d = dfs(v, t, min(f - c, e[i].cap));
            if (d > 0) {
                e[i].cap -= d, e[i^1].cap += d, c += d;
                if (f == c) break;
            } else dep[v] = -1;
        }
    }
    return c;
}

int dinic(int s, int t) {
    int maxflow = 0;
    while (bfs(s, t)){
        maxflow += dfs(s, t, inf);
	}
    return maxflow;
}
//板子结束


//正文开始


bool check(int mid){
	tot = 2; //已有s编号(0),t编号(1) 
	memset(head,-1,sizeof(head));
	for(int i=1;i<=7;i++){ //1.源点->周  容量 mid/7*ee + (mid%7 >=i) * ee
		if(mid%7 >= i){ //最后一个不完整的一周 判是否过了周i
			addedge(s,i,(mid/7)*ee + ee);  //根据当前天数建边的容量
		}else{
			addedge(s,i,(mid/7)*ee);
		}
	}
	for(int i=1;i<=n;i++){
		addedge(i+7,t,c[i]); //2.任务编号i+7->汇点编号t  容量c[i]
		for(int j=1;j<=7;j++){
			if(g[i][j]){
				addedge(j,7+i,inf); //3.周编号j->任务编号7+i inf
			} 
		} 
	}
	int maxflow = dinic(s,t); //跑出最大流 
	return maxflow >= sum;
}

int main(){
    scanf("%d%d",&n,&ee);
    s = 0, t = 7+n+1; //原点为0 汇点为7+n+1 
	sum = 0;
    for (int i=1; i<=n; i++){
        scanf("%d%d", &c[i], &m[i]);
        sum += c[i];
        for (int j = 1, d; j<=m[i]; j++) scanf("%d", &d), g[i][d] = 1;
    }
    int l=1, r=(sum/ee+1)*7+100;
    int ans = 0;
    //满足单调性质: 天数越多,跑出的最大流越大(即能完成的任务数量越多)
    while (l <= r){ //二分出 最少天数 
        int mid = l+r>>1;
        if(check(mid)){ //跑最大流看是否 当前天数下的最大流量>=总任务数 
            ans = mid;
            r = mid - 1;
		}else{
		    l = mid + 1;
		}
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2020-04-19 11:35  fishers  阅读(228)  评论(0编辑  收藏  举报