HDU 2883 kebab 最大流(纯靠建图)
2015-08-17 19:30
417 查看
题意:有一个烤肉串的烤炉可以工作,一次可以烤M个烤肉串,来了N个客人,由第i个客人可知,他在第si时间到达,订了ni个烤肉串,每一个烤肉串需要ti个单位时间,并且要让烤肉串在第ei时间烤完(ei正好拷完也可以),一个客人订的烤肉串只需要在预定的时间内完成即可,无需连续地烧烤直到烤熟,例如:我要从1点烤到8点,烤3串,烤一串需要3小时的时间,则我可以1点到2点烤1小时,4点到5点烤1小时,7点到8点烤1小时。还有一个就是,客户预定的烤串可以分几批进行烧烤。问你所有的客户是否可以被满足?
理解:建图关键!
最早的建图,先按s再按e(s是一个顾客到的时间,e为离开的时间)升序排序,就单纯的一个顾客而言在他的时间段里他要的烤串是一定可以被完成的。所以如果一个区间E(s,e)和它的下一个区间没有公共子区间则不需要把它放到图里面,如下图:
设定E(si,ei)的长度为M*(ei-si),期间需要完成的量为ni*ti,虚拟一个source ss,sink tt。1· s2在E(s1,e1)之间,e2大于e1:ss和第i个顾客连一条边,容量为ni*ti;第i个顾客和第 i个区间(也就是本区间,这里的第i条边要重新标记,可以为n+i)连一条边,容量为 (s2-s1)*M;第i个顾客和第j个区间(枚举之后的边,找到第一个满足条件的区间,注意不要
忘了区间是排过序的,这个j也要重新标记)连一条边,容量为(e1-s2)*M;最后把每一段区间连一条边到tt,容量为(ei-si)*M。
为什么这么建图?
首先我我在区间E里面有ni*ti量的烤串要完成,如果它们可以在s2之前完成,则它可以流过s1到s2区间,如果s1到s2区间不够流的,将它通过另一条管道,连接至下一个满足条件的区间,上图两个区间有重复的地方,重复的地方只能够烤一个顾客的烤串,假设让第二个区间去烤第一个区间s2到e1的烤串的量,而第一个区间不烤自己那部分的烤串的量,则如果第二个区间可以完成第一个区间的余量和本身的量,则表示可以满足这两个顾客的需求。
2· 区间二完全在区间一的里面,即(s1<=s2&&e2<=e1):ss和第i个顾客连一条边,容量为ni*ti;只要之后第j个顾客的区间满足要求,就把第j个顾客和第i个区间连一条边,容量为(ej-sj)*M,再把第i个区间和tt连一条容量为(ei-si)*M的边。这样建图很显然是要在第i个区间里完成满足条件的顾客的需求,想到这里问题来了,我怎么记录一个区间的子区间有哪些,最笨的枚举,可以,太浪费时间,但是今天我想了一下,这个建图是不对的,一个大区间内部加了一个小区间,虽然大区间的总量可以满足小区间的量加上小小区间的量,但是小小区间的量在小区间里可能不能被满足。
这个思路应该是对的,但是太麻烦,但是可以重新划分区间,把所有的s,e点记录在一个数组p里,然后从小到大升序排序,两个相邻的点构成一个区间,由此可以建图,ss到每一个顾客,容量ni*ti,第i个顾客和所有在第i个顾客区间内的p数组里的小区间连一条边,容量为inf(就是说这里的一个路径只起到了连接的作用),再把小区间个tt连接,容量为(p[i]-p[i-1])*M。
解决。
优化建图的代码:
理解:建图关键!
最早的建图,先按s再按e(s是一个顾客到的时间,e为离开的时间)升序排序,就单纯的一个顾客而言在他的时间段里他要的烤串是一定可以被完成的。所以如果一个区间E(s,e)和它的下一个区间没有公共子区间则不需要把它放到图里面,如下图:
设定E(si,ei)的长度为M*(ei-si),期间需要完成的量为ni*ti,虚拟一个source ss,sink tt。1· s2在E(s1,e1)之间,e2大于e1:ss和第i个顾客连一条边,容量为ni*ti;第i个顾客和第 i个区间(也就是本区间,这里的第i条边要重新标记,可以为n+i)连一条边,容量为 (s2-s1)*M;第i个顾客和第j个区间(枚举之后的边,找到第一个满足条件的区间,注意不要
忘了区间是排过序的,这个j也要重新标记)连一条边,容量为(e1-s2)*M;最后把每一段区间连一条边到tt,容量为(ei-si)*M。
为什么这么建图?
首先我我在区间E里面有ni*ti量的烤串要完成,如果它们可以在s2之前完成,则它可以流过s1到s2区间,如果s1到s2区间不够流的,将它通过另一条管道,连接至下一个满足条件的区间,上图两个区间有重复的地方,重复的地方只能够烤一个顾客的烤串,假设让第二个区间去烤第一个区间s2到e1的烤串的量,而第一个区间不烤自己那部分的烤串的量,则如果第二个区间可以完成第一个区间的余量和本身的量,则表示可以满足这两个顾客的需求。
2· 区间二完全在区间一的里面,即(s1<=s2&&e2<=e1):ss和第i个顾客连一条边,容量为ni*ti;只要之后第j个顾客的区间满足要求,就把第j个顾客和第i个区间连一条边,容量为(ej-sj)*M,再把第i个区间和tt连一条容量为(ei-si)*M的边。这样建图很显然是要在第i个区间里完成满足条件的顾客的需求,想到这里问题来了,我怎么记录一个区间的子区间有哪些,最笨的枚举,可以,太浪费时间,但是今天我想了一下,这个建图是不对的,一个大区间内部加了一个小区间,虽然大区间的总量可以满足小区间的量加上小小区间的量,但是小小区间的量在小区间里可能不能被满足。
这个思路应该是对的,但是太麻烦,但是可以重新划分区间,把所有的s,e点记录在一个数组p里,然后从小到大升序排序,两个相邻的点构成一个区间,由此可以建图,ss到每一个顾客,容量ni*ti,第i个顾客和所有在第i个顾客区间内的p数组里的小区间连一条边,容量为inf(就是说这里的一个路径只起到了连接的作用),再把小区间个tt连接,容量为(p[i]-p[i-1])*M。
解决。
优化建图的代码:
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<algorithm> #define inf 0x7fffffff using namespace std; const int N=200+5; const int M=1000+5; int n,m,sum; struct node { int n,s,e,t; }meg ; struct nodee { int u,v,next,flow; }e[N*N*10]; int head[N*3],cnt; int p[N*2],pp; int ss,tt; class Dinic { public: int spath() { queue<int>q; while(!q.empty()) q.pop(); memset(dis,-1,sizeof(dis)); dis[ss]=0; q.push(ss); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i+1;i=e[i].next) { int v=e[i].v; if(dis[v]==-1&&e[i].flow>0) { dis[v]=dis[u]+1; q.push(v); } } } return dis[tt]!=-1; } int Min(int a,int b) { if(a<b) return a; return b; } int dfs(int u,int flow) { int cost=0; if(u==tt) return flow; for(int i=head[u];i+1;i=e[i].next) { int v=e[i].v; if(dis[v]==dis[u]+1&&e[i].flow>0) { int min=dfs(v,Min(e[i].flow,flow-cost)); if(min>0) { e[i].flow-=min; e[i^1].flow+=min; cost+=min; if(cost==flow) break; } else dis[v]=-1; } } return cost; } int result() { int res=0; while(spath()) { res+=dfs(ss,inf); } return res; } private: int dis[N*3]; }dinic; void Init() { memset(head,-1,sizeof(head)); cnt=0; } void add(int a,int b,int c) { e[cnt].u=a; e[cnt].v=b; e[cnt].flow=c; e[cnt].next=head[a]; head[a]=cnt++; e[cnt].u=b; e[cnt].v=a; e[cnt].flow=0; e[cnt].next=head[b]; head[b]=cnt++; } bool cmp(int a,int b) { return a<b; } void Input() { pp=0; for(int i=0;i<n;i++) { scanf("%d%d%d%d",&meg[i].s,&meg[i].n,&meg[i].e,&meg[i].t); p[pp++]=meg[i].s; p[pp++]=meg[i].e; } } void pretreatment() { sum=0; ss=0;tt=3*n+1; sort(p,p+pp,cmp); for(int i=1;i<pp;i++) add(n+i,tt,m*(p[i]-p[i-1])); for(int i=0;i<n;i++) { sum+=meg[i].n*meg[i].t; add(ss,i+1,meg[i].n*meg[i].t); for(int j=1;j<pp;j++) { if(meg[i].s<=p[j-1]&&p[j]<=meg[i].e) add(i+1,j+n,inf); } } } void treatment() { int ans=dinic.result(); if(ans==sum) printf("Yes\n"); else printf("No\n"); } int main() { while(~scanf("%d%d",&n,&m)) { Init(); Input(); pretreatment(); treatment(); } return 0; }
相关文章推荐
- Light OJ 1188 Fast Queries(分块暴力)
- Fork函数初识
- OC_NSString、
- 2440按键模块
- Cell自适应高度
- 用O_APPEND标志open一个文件,能否用lseek在任意位置读写
- AppDelegate.cpp文件详解
- 用O_APPEND标志open一个文件,能否用lseek在任意位置读写
- UVA 10285 Longest Run on a Snowboard
- 1-Android基础知识
- POJ 3669 Meteor Shower
- 好多问题
- 添加主机失败
- HDOJ 1869 六度分离(最短路--dijkstra)
- oracle学习第一弹----逻辑存储结构
- Oracle实现列的自动增长
- 极客学院免费vip
- POJ3038(Blue Jeans)
- Android 事件分发机制详解
- 用adb push/pull init.rc的方法