【Luogu】 P5769 [JSOI2016] 飞机调度

题目链接

点击打开链接

题目解法

考虑可以经停,从 i i i j j j 包括维修在内的最短时间,这是可以通过 f l o y d    O ( n 3 ) floyd\;O(n^3) floydO(n3) 求的
这样我们可以维护出一辆飞机是否可以先运行航班 x x x 再运行航班 y y y,可以通过上面的预处理维护出来
如果一辆飞机可以先运行航班 x x x 再运行航班 y y y,那么我们从 x x x y y y 连一条边
考虑连出的图有何性质?显然这是 D A G DAG DAG

考虑简化后的问题:在一个 D A G DAG DAG 上,有最少的路径覆盖所有的点
这是一个经典问题
考虑拆点,把每个点拆成出点和入点,有边就从入点往出点连边,同时起点向所有入点连边,所有出点向终点连边
结论是: D A G DAG DAG 上的最少路径覆盖 = = = 总点数 − - 最大匹配
证明:考虑某条路径必有终点,且只有终点对应的入点未匹配
反之,所以未匹配的点也一定是某条路径的终点,那么一个未匹配的点就可以对应一条路径
所以 路径的数目就是未匹配点的数目
需要 路径的数目尽量小,所以可得 D A G DAG DAG 上的最少路径覆盖 = = = 总点数 − - 最大匹配
二分图跑 d i n i c dinic dinic 的时间复杂度为 O ( m n ) O(m\sqrt n) O(mn )

时间复杂度为 O ( n 3 + m 2 m ) O(n^3+m^2\sqrt m) O(n3+m2m )

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N(1100),M(600000),inf(0x3f3f3f3f3f3f3f3f);
struct Node{
	int x,y,d;
}airl[N]; 
int n,m,S,T,p[N],t[N][N],d[N][N];
int que[N],hh,tt,dis[N];
int e[M],ne[M],w[M],h[N],cur[N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
bool bfs(){
	memset(dis,0x3f,sizeof(dis));
	hh=0,tt=-1,que[++tt]=S,dis[S]=0,cur[S]=h[S];
	while(hh<=tt){
		int u=que[hh++];
		for(int i=h[u];~i;i=ne[i]){
			int v=e[i];
			if(w[i]&&dis[u]+1<dis[v]) cur[v]=h[v],dis[v]=dis[u]+1,que[++tt]=v;
		}
	}
	return dis[T]!=inf;
}
int find(int u,int limit){
	if(u==T) return limit;
	int res=0;
	for(int i=cur[u];~i&&res<limit;i=ne[i]){
		cur[u]=i;
		int v=e[i];
		if(w[i]&&dis[u]+1==dis[v]){
			int t=find(v,min(w[i],limit-res));
			if(!t) dis[v]=-1;
			res+=t,w[i]-=t,w[i^1]+=t; 
		}
	}
	return res;
}
int dinic(){
	int tot=0,add;
	while(bfs()) while(add=find(S,inf)) tot+=add;
	return tot;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) p[i]=read();
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) t[i][j]=read(); 
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=t[i][j]+p[i]+p[j];
	for(int i=1;i<=n;i++) d[i][i]=p[i];
	for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
		if(i!=k&&k!=j&&j!=i) d[i][j]=min(d[i][j],d[i][k]-p[k]+d[k][j]);
	for(int i=1;i<=m;i++) airl[i].x=read(),airl[i].y=read(),airl[i].d=read();
	memset(h,-1,sizeof(h));
	S=0,T=2*m+1;
	for(int i=1;i<=m;i++) add(S,i,1),add(i,S,0),add(i+m,T,1),add(T,i+m,0);
	for(int i=1;i<=m;i++) for(int j=1;j<=m;j++)
		if(airl[i].d+t[airl[i].x][airl[i].y]+d[airl[i].y][airl[j].x]<=airl[j].d){
//			cout<<i<<' '<<j+m<<'\n';
			add(i,j+m,1),add(j+m,i,0); 
		}
	printf("%lld",m-dinic());
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值