CF 589F 网络流(or贪心?)

题目链接:http://codeforces.com/problemset/problem/589/F


题意:有n盆菜,每个菜的上菜时间是ai-bi,要求每盆菜的品尝时间都相同,求最大的品尝时间。


思路:一开始也想到了网络流,但是没有想到压缩区域。。。(我只想到一个点一个点地建图,这个的图点可能有10^4个啊。。肯定不能用网络流),后来请教别人可以一个区间建图,流量就是区间长度,这个的话点最多300+个。。。网络流妥妥可以。。。。

做法就是找到一个区间,扫一遍所有的菜哪些菜的上菜时间是包含这个区间的,然后从s到时间区间点连一个区间长度大小的流量,时间区间点到能到的菜连一个区间长度大小的流量,最后一遍最大流搞定。

PS:神奇的贪心做法可以二分最大品尝时间,每次扫描一个单位的区间然后把这个区间分给bi最小且品尝时间没满的菜,就这样好了。。。。



#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,b,c) for(int a = b;a <= c;a++)
using namespace std;
typedef long long ll;
const int maxn = 1000005;
const int INF = 0x3f3f3f3f;
const int e_maxn = 100000 * 4;
const int v_maxn = 500;
struct ppp
{
	int v,nex,cap,flow,c;//分别为下一个点,下一条边,当前边容量和流量,还有费用
}e[e_maxn];
int head[v_maxn],dis[v_maxn],cur[v_maxn];//链表头节点,层次图中每个点所属层次,dfs所用的临时链表头
int tole,N,M,s,t;//总加入边数,总输入点数和总输入边数,源点和汇点
void make_edge(int u,int v,int cap,int c)//求最大流时费用不必考虑,可以省去,加上也没关系
{
	e[tole].v = v;e[tole].flow = 0;e[tole].cap = cap;e[tole].c = c;e[tole].nex = head[u];head[u] = tole++;
}
void add_edge(int u,int v,int cap,int c)
{
	make_edge(u,v,cap,c);//正向边容量为cap,费用为c
	make_edge(v,u,0,-c);//反向边容量为0,费用为-c
}
int bfs()//其中s为源点,t为汇点
{
	queue<int> que;
	que.push(s);
	mem(dis,-1);
	dis[s] = 0;
	int temp,v;
	while(!que.empty())
	{
		temp = que.front();
		que.pop();
		for(int i = head[temp];~i;i = e[i].nex)
		{
			v = e[i].v;
			if(dis[v] == -1 && e[i].cap > e[i].flow)
			{
				dis[v] = dis[temp] + 1;
				que.push(v);
			}
		}
	}
	return dis[t] != -1;
}

int dfs(int x,int a)
{
	if(x == t || !a) return a;
	int v,f,ret = 0;
	for(int &i = cur[x];~i;i = e[i].nex)
	{
		v = e[i].v;
		if((dis[v] == dis[x] + 1) && (f = dfs(v,min(a,e[i].cap - e[i].flow))) > 0)
		{
			e[i].flow += f;
			e[i ^ 1].flow -= f;
			a -= f;
			ret += f;
			if(!a)break;
		}
	}
	return ret;
}

void Dinic(int &ans)//ans为最大流
{
	while(bfs())
	{
		for(int i = 0;i <= t;i++)cur[i] = head[i];
		ans += dfs(s,INF);
	}
}

int mark[10005];
int n;
int a[105],b[105];

int cal(int x)
{
	s = 0,t = v_maxn - 1;
	mem(head,-1);
	tole = 0;
	int from = 0;
	int cnt = 0;
	for(int i = 0;i <= 10000;i++){
		if(mark[i]){
			int ok = 0;
			for(int j = 0;j < n;j++){
				if(a[j] <= from && i <= b[j])
				{
					if(!ok){
						cnt++;
						add_edge(s,cnt,i - from,0);
						ok = 1;
					}
					add_edge(cnt,300 + j,i - from,0);
				}
			}
			from = i;
		}
	}
	for(int i = 0;i < n;i++)
		add_edge(300 + i,t,x,0);
	int ans = 0;
	Dinic(ans);
	if(ans == x * n)return 1;
	else return 0;
}


int main()
{
	while(~scanf("%d",&n))
	{
		mem(mark,0);
		int minn = 999999999;
		for(int i = 0;i < n;i++){
			scanf("%d%d",&a[i],&b[i]);
			mark[a[i]] = 1;mark[b[i]] = 1;
			minn = min(minn,b[i] - a[i]);
		}
		int l = 0,r = minn;
		int ans = 0;
		while(l <= r)
		{
			int mid = (l + r) >> 1;
			if(cal(mid)){
				ans = mid;
				l = mid + 1;
			}else r = mid - 1;
		}
		printf("%d\n",ans * n);
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值