蜀传之单刀赴会

题目描述

【题目背景】

公元215年,刘备取益州,孙权令诸葛瑾找刘备索要荆州。刘备不答应,孙权极为恼恨,便派吕蒙率军取长沙、零陵、桂阳三郡。长沙、桂阳蜀将当即投降。刘备得知后,亲自从成都赶到公安(今湖北公安),派大将关羽争夺三郡。孙权也随即进驻陆口,派鲁肃屯兵益阳,抵挡关羽。双方剑拔弩张,孙刘联盟面临破裂,在这紧要关头,鲁肃为了维护孙刘联盟,不给曹操可乘之机,决定当面和关羽商谈。“肃邀羽相见,各驻兵马百步上,但诸将军单刀俱会”。双方经过会谈,缓和了紧张局势。随后,孙权与刘备商定平分荆州,“割湘水为界,于是罢军”,孙刘联盟因此能继续维持。


【问题描述】

关羽受鲁肃邀请,为了大局,他决定冒险赴会。他带着侍从周仓,义子关平,骑着赤兔马,手持青龙偃月刀,从军营出发了,这就是历史上赫赫有名的“单刀赴会”。关羽平时因为军务繁重,决定在这次出行中拜访几个多日不见的好朋友。然而局势紧张,这次出行要在限定时间内完成,关公希望你能够帮助他安排一下行程,安排一种出行方式,使得从军营出发,到达鲁肃处赴会再回来,同时拜访到尽可能多的朋友,在满足这些条件下行程最短。注意拜访朋友可以在赴会之前,也可以在赴会之后。现在给出地图,请你完成接下来的任务。

输入

第一行n,m,k,tn,m,k,tn,m,k,t,代表有nnn个地点,mmm条道路,有kkk个朋友(不包括鲁肃),以及限定时间ttt(行走111单位长度的路程用时111单位时间)。
接下来m行,每行有x,y,wx,y,wx,y,w三个整数,代表xxxyyy之间有长度为www的道路相连。
接下来一行有kkk个整数,代表朋友所在的都城编号(保证两两不同,且不在111nnn)
(我们约定1是关羽的营地,n是鲁肃的营地)

输出

输出两个整数,分别是最多可以拜访的朋友数,以及在这种情况下最少需要耗费的时间,如果连到达鲁肃再回来都无法完成,输出一个−1-11就可以了。

样例输入

555 777 222 151515
111 222 555
111 333 333
222 333 111
222 444 111
333 444 444
222 555 222
444 555 333
222 444

样例输出

222 141414

提示

【数据规模和约定】
101010%数据,n≤10,m≤50,k≤5n≤10,m≤50,k≤5n10,m50,k5;
101010%数据,k=0k=0k=0;
101010%数据,k=1k=1k=1;
303030%数据,k≤5k≤5k5;
对于100100100%数据,n≤10000,m≤50000,k≤15,t≤2147483647,w≤10000n≤10000,m≤50000,k≤15,t≤2147483647,w≤10000n10000,m50000,k15,t2147483647,w10000


解析

首先,我们会发现其实有用点只有kkk个朋友与起点、终点共k+2k+2k+2个点。
k≤15k≤15k15,因此数据范围急剧下降。
因为要求最短路,我们可以先对这些点以各点为源点做k+2k + 2k+2次单源最短路,统计出这些有用点之间的最短路
之后考虑状压(kkk那么小,不状压还能咋做?)
f[sta][i]f[sta][i]f[sta][i]表示已经走过的点的集合为stastasta,当前在iii的最少时间
f[sta][i]=min(f[sta′][j]+dis(i,j))(j∈sta′,sta′⊂sta,sta−sta′=f[sta][i] = min(f[sta'][j] + dis(i , j))(j∈sta',sta'⊂sta,sta - sta' =f[sta][i]=min(f[sta][j]+dis(i,j))(jsta,stasta,stasta= { iii } )))

最后统计答案时,优先保证状态合法(时间要≤t≤tt)且111的个数尽量多,再考虑路径最短。
注意:DP出来的结果是不回到起点的,最终统计时要对f[sta][i]+dis(i,1)f[sta][i] + dis(i , 1)f[sta][i]+dis(i,1)minminmin

代码
#include<queue>
#include<stdio.h>
#include<cstring>
#define mp(x , y) make_pair(x , y)
using namespace std;
const int maxk = 20;
const int maxn = 10005;
const int maxe = 50005;
const int oo = 1000000000;
priority_queue < pair < int , int > , vector < pair < int , int > > , greater < pair < int , int > > > Heap;
int d[maxn];
int n;
int edgenum;
int Next[maxe << 1] , vet[maxe << 1] , val[maxe << 1] , head[maxn];
int a[maxk] , D[maxk][maxk];
int f[1 << maxk][maxk] , one_num[1 << maxk];
int min(int x , int y){return x < y ? x : y;}
void add_edge(int u , int v , int cost)
{
	edgenum++;
	Next[edgenum] = head[u];
	vet[edgenum] = v;
	val[edgenum] = cost;
	head[u] = edgenum;
}
void Dijkstra(int s)
{
	for(int i = 1;i <= n;i++) d[i] = oo;
	d[s] = 0;
	Heap.push(mp(d[s] , s));
	while(!Heap.empty())
	{
		int u = Heap.top().second , dis = Heap.top().first;
		Heap.pop();
		if(d[u] != dis) continue;
		for(int e = head[u];e;e = Next[e])
		{
			int v = vet[e];
			if(d[v] > d[u] + val[e]) d[v] = d[u] + val[e] , Heap.push(mp(d[v] , v));
		}
	}
}
int main()
{
	int m , k ,t;
	scanf("%d%d%d%d",&n,&m,&k,&t);
	while(m--)
	{
		int u , v , cost;
		scanf("%d%d%d",&u,&v,&cost);
		add_edge(u , v , cost);
		add_edge(v , u , cost);
	}
	Dijkstra(1);
	if(d[n] + d[n] > t)
	{
		puts("-1");
		return 0;
	}
	a[1] = 1;
	for(int i = 2;i <= k + 1;i++) scanf("%d",&a[i]);
	a[k + 2] = n;
	k += 2;
	for(int i = 1;i <= k;i++)
	{
		Dijkstra(a[i]);
		for(int j = 1;j <= k;j++) D[i][j] = D[j][i] = d[a[j]];
	}
	memset(f , 0x3f3f3f , sizeof f);
	f[1][1] = 0;
	for(int sta = 2;sta < (1 << k);sta++)
	{
		for(int i = 1;i <= k;i++)
		{
			if(!(sta & (1 << i - 1))) continue;
			for(int j = 1;j <= k;j++)
			{
				if(!(sta & (1 << j - 1))) continue;
				f[sta][i] = min(f[sta][i] , f[sta - (1 << i - 1)][j] + D[i][j]);
			}
		}
	}
	one_num[0] = 0;
	for(int i = 1;i < (1 << k);i++) one_num[i] = one_num[i >> 1] + (i & 1);
	int cnt = 2 , ans = oo;
	for(int sta = 0;sta < (1 << k);sta++)
	{
		if(!(sta & (1 << k - 1)) || !(sta & 1)) continue;
		if(one_num[sta] < cnt) continue;
		int res = oo;
		for(int i = 1;i <= k;i++)
		{
			if(!(sta & (1 << i - 1))) continue;
			res = min(res , f[sta][i] + D[i][1]);
		}
		if(res > t) continue;
		if(one_num[sta] == cnt) ans = min(ans , res);
		else if(one_num[sta] > cnt) ans = res , cnt = one_num[sta];
	}
	printf("%d %d\n",cnt - 2 , ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值