[POI2007]ATR-Tourist Attractions [TPLY]

[POI2007]ATR-Tourist Attractions
题目链接(https://www.luogu.org/problemnew/show/P3451)

这种稠密图还是建议你不要跑spfa,你跑dijkstra堆优化会快很多
要看原图戳我(左下角被洛谷图标遮住了)

题意

题目给你的意思就是
求1到n的
必须经过一些点(2→k+1)
而且过这些点还要讲先后顺序
的最短路长度

解题

首先看到k<=20
它这是告诉你
对于这k个必须经过的点
你怎么暴力怎么搞
所以我们对这k个点每个点单元最短路(dijkstra)一下,求出他们到所有点的距离。dis[i][j]表示由i出发到j的距离

然后是处理先后关系。我们建立一个数组r[i]r[i]的值,表示到达第i个点之前,必须停留的点的状压集合,1为必经,0为无所谓(因为k<=20所以可以状压)

接着就是状压DP。这里f[i][j]表示当前状态集合为i(1为停留过,0为没有),目前停留在j的最短路径。
转移就是普通状压dp套路,从0到(1<<k-1)[全都有] 枚举状态,找到一个集合中存在的点和一个集合中不存在的点,如果当前状态满足这个不在集合内的点的r[i](也就是经过它之前必须经过的点都经过了)那么就进行转移。
初始状态,f设为INF,如果一个点i在停留之前不需要在任何点停留,那么f[1][i]=dis[1][i]f[0][1]=0

几点注意(长者的经验教训)

1.当k=0时直接跑最短路不然会WA第六个点
2.INF不能开太大(第三个点会爆成负数)
3.数组要卡空间,不然要么RE要么MLE
4.如果数组太大最好不用memset,最好自己给数组赋值,这样会快很多
5.注意位运算的先后顺序,能打括号就打括号。

代码

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

#define rg register int
#define RG register
#define ll long long
#define il inline
#define INF 1000000000 // INF 不要太大会飞起
#define mk make_pair
using namespace std;
typedef pair <int,int> P;

il int gi()
{
    rg x=0,o=0;RG char ch=getchar();
    while((ch!='-')&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') o=1,ch=getchar();
    while('0'<=ch&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return o?-x:x;
}

struct Edge{int to,nxt,w;}e[300001];
int Ehead[30001],Ecnt=1;
il void Eadd(rg u,rg v,rg w)
{
    e[Ecnt]=(Edge){v,Ehead[u],w};
    Ehead[u]=Ecnt++;
    e[Ecnt]=(Edge){u,Ehead[v],w};
    Ehead[v]=Ecnt++;
}

int n,m,k,g;
int r[32],dis[32][30001];
priority_queue <P,vector<P>,greater<P> > Q;
il void dijkstra(rg rt)
{
    for(rg i=1;i<=n;++i) dis[rt][i]=INF;
    while(!Q.empty()) Q.pop();
    Q.push(mk(0,rt));dis[rt][rt]=0;
    while(!Q.empty())
    {
        rg u=Q.top().second;Q.pop();
        for(rg i=Ehead[u];i;i=e[i].nxt)
        {
            rg v=e[i].to;
            if(dis[rt][v]>dis[rt][u]+e[i].w)
            {
                dis[rt][v]=dis[rt][u]+e[i].w;
                Q.push(mk(dis[rt][v],v));
            }
        }       
    }
}
// dijkstra 无vis数组

int f[1<<20][25],Ans=INF;
int a,b,u,v,w;
int main()
{
    n=gi(),m=gi(),k=gi();
    for(rg i=1;i<=m;++i)
    {
        u=gi(),v=gi(),w=gi();
        Eadd(u,v,w);    
    }

    if(!k) 
    {
        dijkstra(1);
        printf("%d",dis[1][n]);
        return 0;
    } //不加这个判断第6个点会WA

    g=gi();
    for(rg i=1;i<=g;++i)
    {
        a=gi(),b=gi();
        r[b] |= (1<<(a-2));
    }
    for(rg i=1;i<=k+1;++i)  dijkstra(i);
    
    for(rg i=0;i<=(1<<k)-1;++i)
     for(rg j=1;j<=k+1;++j)
      f[i][j]=INF;
    // 数组大就尽量不用memset
    
    f[0][1]=0;
    for(rg i=2;i<=k+1;++i)
     if(!r[i])
      f[1<<(i-2)][i]=dis[1][i];
    
    for(rg i=1;i<=(1<<k)-1;++i)
     for(rg j=0;j<k;++j)
      if(i&(1<<j))
       for(rg l=0;l<k;++l)
        if( !(i&(1<<l)) && (i|r[l+2])==i )
         f[i|(1<<l)][l+2]=min(f[i|(1<<l)][l+2],f[i][j+2]+dis[j+2][l+2]);        
    
    for(rg i=2;i<=k+1;++i)
     Ans=min(Ans,f[(1<<k)-1][i]+dis[i][n]);         
    printf("%d",Ans);
    return 0;
}

转载于:https://www.cnblogs.com/tply/p/8274275.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值