UVALive 3126 Taxi Cab Scheme
DAG最小路径覆盖
题意
现在有n个客户,他们要乘出租车,他们每个人,有一个出发时间t,起点位置和终点位置。现在要安排尽量少的出租车送他们到目的地。出租车必须在客户出发前至少提前一分钟赶到那个客户出发的位置才行,或者这个客户就是这辆出租车的第一个乘客。让你输出需要的最少的出租车数。
思路
DAG的最小路径覆盖。先开始我们建边,如果客户u和客户v能用一辆出租车搞定,那么就连一条边,由于时间是天然顺序,所以这个连出来的图,不会有环,即是一个 DAG。那么我们的问题就变成了,找出最少的路径个数,使得每个点都恰好在一条路径上。这就是DAG的最小路径覆盖问题。
算法过程是:我们把一个点对等的拆成两个点,一个放左边,一个放右边。然后像之前那样左右连边。然后找最大匹配,匹配数等于我们不需要另找出租车的客户数。为什么?因为想想怎么做的最大匹配,找增广路,对,那么这就相当于再找一条简单路径。每次找到一条增广路,就相当于多了一个节点在同一路径上,也就是不要再另外开路径了。也就是我们只要就解决最多能找到多少条增广路,也就是最大匹配了。所以答案就是 n-最大匹配数。
综上:
DAG的最小路径覆盖:一个DAG图,在图中找出尽量少的路径,使每个节点严格属于一条路径。
DAG的最小路径覆盖 = n-最大匹配数。
这个算法也同样适用于带权的DAG,而不适用于非DAG的图。
最后的匹配结果中,比如1到2*有匹配,2到3*有匹配,那么路径覆盖就是,1到2到3。
柯尼希定理:二分图最小点覆盖的点数=最大匹配数。
最小路径覆盖的边数=顶点数n-最大匹配数最大独立集=最小路径覆盖=顶点数n-最大匹配数
增广路定理:用未盖点表示不与任何匹配边邻接的点,其他点位匹配点,即恰好和一条匹配边临界的点。从未盖点出发,依次经过非匹配边,匹配边,非匹配边,匹配边。。。所得到的路径称为交替路。注意,如果交替路的终点时一个未盖点,则称这条交替路位一条增广路。在增广路中,非匹配边比匹配边多一条。增广路的作用是改进匹配。如果有一条增广路,那么把此路上的匹配边和非匹配边互换,得到的匹配比刚才多一边。反过来,如果找不到增广路,则当前匹配就是最大匹配。
查找增广路,存在增广路就交换增广路上的非匹配边和匹配边,这样会使得当前最大匹配数+1。
DAG的最小路径覆盖
定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。
最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。
最小不相交路径覆盖:每一条路径经过的顶点各不相同。如图,其最小路径覆盖数为3。即1->3>4,2,5。
最小可相交路径覆盖:每一条路径经过的顶点可以相同。如果其最小路径覆盖数为2。即1->3->4,2->3>5。
特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
DAG的最小不相交路径覆盖
算法:把原图的每个点V拆成VxVx和VyVy两个点,如果有一条有向边A->B,那么就加边Ax−>ByAx−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。
证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。
DAG的最小可相交路径覆盖
算法:先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。
证明:为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。
代码
#include<bits/stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
typedef long long LL;
const int MAXN=1007;
using namespace std;
struct BPM
{
int n, m;
vector<int > G[MAXN];
int left[MAXN];
int right[MAXN];
bool T[MAXN];
bool S[MAXN];
void init(int n,int m)
{
this->n = n;
this->m = m;
for(int i = 1; i <= n; i++) G[i].clear();
}
void addEdge(int u, int v)
{
G[u].push_back(v); //建边
}
bool match(int u)
{
S[u] = true; //标记右边的点u
for(int i = 0; i < G[u].size(); i++) //遍历由u点出发,连接的左边的点
{
int v = G[u][i];
if(!T[v]) //左边的没标记过的点, 走没匹配过的边
{
T[v] = true;
if(left[v] == -1 || match(left[v])) //走匹配过的边到右边的点
{
left[v] = u;
right[u] = v;
return true;
}
}
}
return false;
}
int solve()
{
memset(left, -1, sizeof(left));
memset(right, -1, sizeof(right));
int ans = 0;
for(int u = 1; u <= n; u++)
{
memset(S, 0, sizeof(S));
memset(T, 0, sizeof(T));
if(match(u)) ans++; //先用匈牙利算法求出最大匹配
}
return ans;
}
} bpm;
vector<int> G[MAXN];
struct Peo
{
int tm;
int sposx,sposy;
int tposx,tposy;
int getT()
{
return abs(tposx-sposx)+abs(tposy-sposy);
}
}peo[MAXN];
inline int getTime(const Peo &a,const Peo &b)
{
return abs(a.tposx-b.sposx)+abs(a.tposy-b.sposy);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,b;
scanf("%d:%d",&a,&b);
peo[i].tm=a*60+b;
scanf("%d %d %d %d",&peo[i].sposx,&peo[i].sposy,&peo[i].tposx,&peo[i].tposy);
}
bpm.init(n,n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i!=j)
{
if(peo[i].tm+peo[i].getT()+1+getTime(peo[i],peo[j])<=peo[j].tm)
{
bpm.addEdge(i,j+n);
}
}
}
}
int res=bpm.solve();
printf("%d\n",n-res);
}
return 0;
}