LA7984—Delivering Goods(最短路+DAG上的最少路径覆盖)

题目链接:传送门



题目大意:给定n个点,m条有向边,c个需要被访问的点,c个点都要在最短的时间内被访问到。问一些卡车从0点出发,访问c个点,最少需要多少辆卡车。


解题思路:对于那c个点,若任意两个点间在同一个最一最短路径上,则在两个点间连边,这样将构成一个DAG。那么问题就变成了最少需要多少条路径才能覆盖该图上的所有点,即DAG上的最小路径覆盖问题。

白书上的解法:建立二分图,对于DAG(结点数为n)上的点u将其拆成u和u',若存在u-->v的有向边,在u,v'间建边,求得最大匹配m,最后的答案为n-m。


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>

using namespace std;
typedef long long ll;

const int N = 5010;
const int M = 300100;
const ll INF = 0x3ffffffffffffffLL;

//DAG上的最小路径覆盖

struct Edge{
	int node; ll len;
	Edge*next;
}m_edge[M];
Edge*head[N];
int Ecnt,flag[N];
ll dist[N],dist2[N];

struct Node{
	int node;
	Node*next;
}m_node[M];
int girl[N],tick[N],Ncnt;
Node*Ver[N];

int C[N];

void init()
{
	Ecnt = Ncnt = 0;
	fill(head,head+N,(Edge*)0);
	fill(Ver,Ver+N,(Node*)0);
}

void mkEdge(int a,int b,ll c)
{
	m_edge[Ecnt].node = b;
	m_edge[Ecnt].len = c;
	m_edge[Ecnt].next = head[a];
	head[a] = m_edge+Ecnt++;
}

void mkNode(int b,int g)
{
	m_node[Ncnt].node = g;
	m_node[Ncnt].next = Ver[b];
	Ver[b] = m_node+Ncnt++;
}

struct HeapNode{
	int u; ll d;
	HeapNode(ll _d,int _u){
		d = _d; u = _u;
	}
	bool operator < (const HeapNode&a)const{
		return d > a.d;
	}
};

void dijkstra(int s,ll dist[])
{
	priority_queue<HeapNode>Q;
	fill(dist,dist+N,INF);
	fill(flag,flag+N,0);
	dist[s] = 0;
	Q.push(HeapNode(0,s));

	while(!Q.empty()){
		HeapNode x = Q.top();
		Q.pop();
		int u = x.u;
		if(flag[u]) continue;
		flag[u] = 1;
		for( Edge*p = head[u] ; p ; p = p->next ){
			int v = p->node;
			if(dist[v] > dist[u]+p->len){
				dist[v] = dist[u]+p->len;
				Q.push(HeapNode(dist[v],v));
			}
		}
	}
}

//二分匹配
bool find(int x)
{
	for( Node*p = Ver[x] ; p ; p = p->next ){
		int s = p->node;
		if(!tick[s]){
			tick[s] = 1;
			if(girl[s] == 0||find(girl[s])){
				girl[s] = x;
				return true;
			}
		}
	}
	return false;
}

//将需要访问的点,且某两点间在同一最短路径上的话就建边
void Build(int n,int c)
{
	for( int i = 0 ; i < c ; ++i ){
		dijkstra(C[i],dist2);
		for( int j = 0 ; j < c ; ++j ){
			//两点在同一最短路径上
			if(i != j && dist2[C[j]] != INF && dist[C[i]]+dist2[C[j]] == dist[C[j]]){
				mkNode(C[i],C[j]+n);
			}
		}
	}
}

int solve(int n,int c)
{
	Build(n,c);
	
	int cnt = 0;
	fill(girl,girl+N,0);
	for( int i = 0 ; i < c ; ++i ){
		fill(tick,tick+N,0);
		if(find(C[i])) cnt++;
	}
	return cnt;
}


int main()
{
	//freopen("delivering-29.in","r",stdin);
	//freopen("yuan.txt","w",stdout);
	int n,m,c,cas = 0;
	while(~scanf("%d%d%d",&n,&m,&c)&&n&&m&&c){
		init();
		for( int i = 0 ; i < c ; ++i ){
			scanf("%d",&C[i]);
		}

		int a,b; ll d;
		for( int i = 0 ; i < m ; ++i ){
			scanf("%d%d%lld",&a,&b,&d);
			mkEdge(a,b,d);
		}

		dijkstra(0,dist);

		printf("Case %d: %d\n",++cas,c-solve(n, c));
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值