蜀传之单刀赴会

题目描述

【题目背景】

公元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,幸好这题全对。




评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值