题解 UVA1201 【Taxi Cab Scheme】

· · 题解

题目传送门

算法分析:DAG 最小路径点覆盖

我们考虑建图来表示题中所示的关系。当一辆出租车结束当前任务并且能够在下一个任务开始前赶到目的地,我们就认为这两个任务是可能被同一辆出租车完成的,因此我们在这前一个任务到后一个任务之间连一条有向边,表示连续完成的关系。那么,用尽可能少的出租车完成这些任务,就是选择尽可能少的起点沿着边往下走,使图中每一个节点都被遍历到。于是将题意简化如下:

给定一张 DAG,求这张 DAG 的最小路径点覆盖

为了方便理解,在讲述时利用拆点的思想(在代码实现中不需要特意拆点,在下面的代码里会体现这一点)。我们对于原来的有向图,把每个点 x 拆成 xx+n 两个点,建立一张新的二分图,把原来的点作为左部点,把新拆出来的点作为右部点,对于原来的每条有向边(x,y),在新图上对应连一条边(x,y+n)。那么问题转化为:

给定一张二分图,求其最小点覆盖。

为什么有向无环图的最小路径点覆盖等于拆点得到的二分图的最小点覆盖呢?请看下图(省略原边):

电脑自带画图真难用。

观察图中的一种可行最大匹配(1,2\prime),(2,5\prime),找到一条路径 1\to 2\prime \to 2\to 5\prime\to 5,可以理解为,每找到一对匹配,就将这两个点并到一个“集合”里。在题目中,就是归属同一辆出租车管。那么最后就是看有几个“集合”,即需要几辆出租车。由此可见,在拆点得到的二分图上得到的最小点覆盖恰好等于有向无环图的最小路径点覆盖。根据 König 定理,二分图最小点覆盖包含的点数等于二分图最大匹配包含的边数。于是我们用 n 减去最大匹配就得到了最后答案。

有向无环图最小路径点覆盖的严格证明请看这里。

König 定理证明(转载自 Matrix67 的博客)

最后上代码:

#include<bits/stdc++.h>
#define reg register
#define F(i,a,b) for(reg int i=(a);i<=(b);i++)
using namespace std;
const int N=1e3+10,M=1e6+1;
int n,mch[N],T,tmp1,tmp2;
int ver[M],net[M],head[M],tot;
bool vis[N];
char ccc;
struct P {
    int t,stx,sty,edx,edy; 
    inline void init() {
        cin>>tmp1>>ccc>>tmp2>>stx>>sty>>edx>>edy;
        t=tmp1*60+tmp2;//转化下时间,方便计算 
    }
    bool operator <(const P& a)const {
        return t<a.t;//将较早的任务放到前面 
    }
} a[N];
inline int dis(int x,int y,int a,int b){//曼哈顿距离 
    return abs(x-a)+abs(y-b);
}
inline void add(int x,int y){//x -> y
    ver[++tot]=y,net[tot]=head[x],head[x]=tot;
}
bool dfs(int x) {
    for(reg int i=head[x]; i; i=net[i]) {
        int &pos=ver[i];
        if(vis[pos])continue;
        vis[pos]=1;
        if(!mch[pos] or dfs(mch[pos])) {
            mch[pos]=x;
            return true;
        }
    }
    return false;
}
int main() {
    cin.tie(0);
    cin>>T;
    while(T--) {
        cin>>n;
        memset(mch,0,sizeof(mch));//注意清空 
        memset(ver,0,sizeof(ver));
        memset(head,0,sizeof(head));
        memset(net,0,sizeof(net));
        tot=0;
        F(i,1,n)a[i].init();
        int ans=0;
        sort(a+1,a+n+1);//题中并没有按照时间顺序给出,记得排序 
        F(i,1,n){
            F(j,i+1,n){
                int t1=dis(a[i].stx,a[i].sty,a[i].edx,a[i].edy);
                //本次任务用时 
                int t2=dis(a[i].edx,a[i].edy,a[j].stx,a[j].sty);
                //走到下一个任务位置用时 
                if(a[j].t>a[i].t+t1+t2)add(i,j);
                //能够接到下一个任务,就连边 
            }
        }
        F(i,1,n) {//二分图最大匹配 
            memset(vis,0,sizeof(vis));
            ans+=dfs(i);
        }
        cout<<n-ans<<endl;//最小覆盖 
    }
    return 0;
}

AC记录

欢迎交流讨论,请点个赞哦~