拜访女神

题目描述

TRT出国后,想找一个好的位置住下来。而他所在的城市,恰好有N栋建筑(从1~n编号),他会选择这些建筑的某一个居住。而建筑之间,有M条双向路相连。每条道路有一个起始点u,终止点v,以及走过这条道路所需的时间d。所有建筑都可以借助一些道路相互到达。TRT每天会从他的住房出发,按任意顺序拜访他的K个女神(他想怎么走就怎么走),不过由于TRT精力有限,他的女神个数不会超过12个。可他的女神们都比较娇气,希望TRT尽快来看她们(可她们却不会担心TRT的花心。。。)。对于第i个女神,她住在第p[i]栋建筑物,她每等x分钟(从TRT离家的那一时刻开始计算),她的焦急程度(初始为0)就会增加x。TRT当然希望她们高兴越好,而且他也不会让某个女神特别伤心,所以他希望所有女神的焦急程度的最大值越小越好。且他也不希望与任何一个女神住在一起,要不然会被众人黑成傻逼的(虽然他已经被我们黑成傻逼了)。所以他向你求助,帮他找出他应该住的那栋建筑,以及此时所有女神焦急程度最大值。

题解

观察到起点的选择并不会影响到之后在女神之间的路程。女神那么少当然是状压辣。所以可以先做女神的DP,先预处理出每个女神的最短路,考虑转移,只有一维已经拜访了谁似乎并不是很好转移,于是我们要发扬什么恶心就把什么放到维度上面去的做法,存一维表示最后拜访的是谁,这样就能转移了。家的答案只要 O ( n k ) O(nk) O(nk)扫一遍就可以了

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define maxn 10005
#define MAXN 50005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
    LL res=0,f=1; char c;
    while(!isdigit(c=getchar())) if(c=='-') f=-1;
    res=c^48;
    while(isdigit(c=getchar())) res=(res<<1)+(res<<3)+(c^48);
    return res*f;
}
struct EDGE{
    int u,v,w,nxt;
}e[MAXN<<1];
struct NODE{
    int u,d;
    bool operator < (const NODE&rhs) const {return d>rhs.d;} 
};
int cnt,head[maxn];
void add(int u,int v,int w){
    e[++cnt]=(EDGE){u,v,w,head[u]};
    head[u]=cnt;
}
priority_queue<NODE> Q;
int d[12][maxn];
void Dijstra(int dis[],int s){
    Q.push((NODE){s,0}); dis[s]=0;
    while(!Q.empty()){
        int u=Q.top().u,d=Q.top().d; Q.pop();
        if(d!=dis[u]) continue;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v,w=e[i].w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                Q.push((NODE){v,dis[v]});
            }
        }
    }
}
int n,m,k,p[12],f[12][12][1<<12],ans[12],Ans,Ans_u;
bool vis[maxn];
int main(){
    memset(d,0x3f,sizeof d); memset(f,0x3f,sizeof f); memset(ans,0x3f,sizeof ans); Ans=INF;
    n=read(); m=read(); k=read();
    for(int i=0;i<k;i++) p[i]=read(),vis[p[i]]=1;
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),w=read();
        add(x,y,w); add(y,x,w);
    }
    for(int i=0;i<k;i++) Dijstra(d[i],p[i]);
    for(int i=0;i<k;i++){
        f[i][i][1<<i]=0;
        for(int s=0;s<1<<k;s++)if(s&1<<i){
            for(int j=0;j<k;j++)if(s&1<<j){
                for(int l=0;l<k;l++)if(!(s&1<<l)){
                    f[i][l][s|1<<l]=min(f[i][l][s|1<<l],f[i][j][s]+d[j][p[l]]);
                }
            }
        }
        for(int j=0;j<k;j++) ans[i]=min(ans[i],f[i][j][(1<<k)-1]);
    }
    for(int i=1;i<=n;i++)if(!vis[i]){
        for(int j=0;j<k;j++){
            if(ans[j]+d[j][i]<Ans){
                Ans=ans[j]+d[j][i];
                Ans_u=i;
            }
        }
    }
    printf("%d %d\n",Ans_u,Ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jarden_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值