题目描述
【题目背景】
公元215年,刘备取益州,孙权令诸葛瑾找刘备索要荆州。刘备不答应,孙权极为恼恨,便派吕蒙率军取长沙、零陵、桂阳三郡。长沙、桂阳蜀将当即投降。刘备得知后,亲自从成都赶到公安(今湖北公安),派大将关羽争夺三郡。孙权也随即进驻陆口,派鲁肃屯兵益阳,抵挡关羽。双方剑拔弩张,孙刘联盟面临破裂,在这紧要关头,鲁肃为了维护孙刘联盟,不给曹操可乘之机,决定当面和关羽商谈。“肃邀羽相见,各驻兵马百步上,但诸将军单刀俱会”。双方经过会谈,缓和了紧张局势。随后,孙权与刘备商定平分荆州,“割湘水为界,于是罢军”,孙刘联盟因此能继续维持。
【问题描述】
关羽受鲁肃邀请,为了大局,他决定冒险赴会。他带着侍从周仓,义子关平,骑着赤兔马,手持青龙偃月刀,从军营出发了,这就是历史上赫赫有名的“单刀赴会”。关羽平时因为军务繁重,决定在这次出行中拜访几个多日不见的好朋友。然而局势紧张,这次出行要在限定时间内完成,关公希望你能够帮助他安排一下行程,安排一种出行方式,使得从军营出发,到达鲁肃处赴会再回来,同时拜访到尽可能多的朋友,在满足这些条件下行程最短。注意拜访朋友可以在赴会之前,也可以在赴会之后。现在给出地图,请你完成接下来的任务。
输入
第一行n,m,k,t,代表有n个地点,m条道路,有k个朋友(不包括鲁肃),以及限定时间t(行走1单位长度的路程用时1单位时间)。
接下来m行,每行有x,y,w三个整数,代表x和y之间有长度为w的道路相连。
接下来一行有k个整数,代表朋友所在的都城编号(保证两两不同,且不在1和n)
(我们约定1是关羽的营地,n是鲁肃的营地)
输出
输出两个整数,分别是最多可以拜访的朋友数,以及在这种情况下最少需要耗费的时间,如果连到达鲁肃再回来都无法完成,输出一个-1就可以了。
样例输入
5 7 2 151 2 51 3 32 3 12 4 13 4 42 5 24 5 32 4
样例输出
2 14
提示
【数据规模和约定】
有10%数据,n<=10,m<=50,k<=5;
有10%数据,k=0;
有10%数据,k=1;
另30%数据,k<=5;
对于100%数据,n<=10000,m<=50000,k<=15,t<=2147483647,w<=10000
跟前面那个地图寻宝是一样的思考方式,不过要用堆优化的dij,打得累死。。。
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std; //s=1 t=n 要回到起点
const ll oo=10000000000LL;
int n,m,edge,Time,tot,all,x,y,z,ty;
ll ans1,ans2;
int head[10005],Next[100005],to[100005],len[100005];
int b[20],heap[10005],pos[10005];
ll f[66000][20],d[20][20],dis[10005];
void add(int x,int y,int z)
{
tot++;
Next[tot]=head[x];
to[tot]=y;
len[tot]=z;
head[x]=tot;
}
int work(int x)
{
int s=0;
while(x>0)
{
if(x%2==1) s++;
x=x>>1;
}
return s;
}
void dp()
{
all=(1<<m)-1;
f[0][0]=0;
for(int i=1;i<=all;i++)
for(int j=1;j<=m;j++) f[i][j]=oo;
for(int i=1;i<=all;i++)
for(int j=1;j<=m;j++)
{
int v=(1<<(j-1));
if((i&v)!=v) continue;
int pre=i^v;
if(pre==0)
{
f[i][j]=min(f[i][j],f[0][0]+d[0][j]);
continue;
}
for(int k=1;k<=m;k++)
if(j!=k)
{
int w=(1<<(k-1));
if((pre&w)!=w) continue;
f[i][j]=min(f[i][j],f[pre][k]+d[k][j]);
}
}
ans1=0;
ans2=oo*2;
int v=(1<<(m-1));
for(int i=all;i>=1;i--)
if((i&v)==v) //有鲁肃
for(int j=1;j<=m;j++)
{
int w=(1<<(j-1));
if((i&w)!=w) continue; //枚举终点
int Friend=work(i)-1; //有几个朋友
ll total=f[i][j]+d[j][0];
if(total<=Time)
{
if(Friend>ans1)
{
ans1=Friend;
ans2=total;
}
else
if(Friend==ans1&&total<ans2)
{
ans1=Friend;
ans2=total;
}
}
}
if(ans2==oo*2) cout<<"-1";else cout<<ans1<<' '<<ans2;
}
void up(int id)
{
while(id/2>=1)
{
int x=heap[id];
int y=heap[id/2];
if(dis[x]<dis[y])
{
swap(heap[id],heap[id/2]);
swap(pos[x],pos[y]);
id=id/2;
}
else break;
}
}
void down(int id)
{
int j;
while(id*2<=ty)
{
int x=heap[id*2];
int y=heap[id*2+1];
if(id*2+1>ty||dis[x]<dis[y]) j=id*2; else j=id*2+1;
x=heap[id];
y=heap[j];
if(dis[x]>dis[y])
{
swap(heap[id],heap[j]);
swap(pos[x],pos[y]);
id=j;
}
else break;
}
}
void dij(int x)
{
for(int i=1;i<=n;i++)
{
dis[i]=oo;
pos[i]=0;
}
dis[b[x]]=0;
ty=1;
heap[1]=b[x];
pos[b[x]]=1;
while(ty>0)
{
int y=heap[1];
pos[y]=0;
swap(heap[1],heap[ty]);
swap(pos[heap[1]],pos[heap[ty]]);
ty--;
down(1);
for(int i=head[y];i!=-1;i=Next[i])
if(dis[y]+len[i]<dis[to[i]])
{
dis[to[i]]=dis[y]+len[i];
if(pos[to[i]]==0)
{
ty++;
heap[ty]=to[i];
pos[to[i]]=ty;
}
up(pos[to[i]]);
}
}
for(int i=0;i<=m;i++) d[x][i]=dis[b[i]];
}
int main()
{
cin>>n>>edge>>m>>Time;
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=1;i<=edge;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
b[0]=1;
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
m++;
b[m]=n;
for(int i=0;i<=m;i++) dij(i);
dp();
return 0;
}
后记:7.22考试时,此题总算浮出水面(我的内心是狂喜的)。位运算的括号漏加让我调了好久(幸好打了对拍)。T1脑残爆0,幸好这题全对。