牛客上大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;
}