[NOIP2017模拟]长跑路径

2017.10.14 T3 1982

样例数据
输入

1
5 6
1 2 1
2 3 3
3 1 3
2 5 1
2 4 2
4 3 1
3
1 3 5

输出

2

分析:这是一道很妙的题(考试的时候只会40%的暴力)。
100%:把可以选的点分成两部分,一部分当起点,一部分当终点,转化成多起点多终点的最短路,建立一个对起点点集建立超级起点,跑一次得到这种分法之间的最短路。
但这也许并不是正解,因为也许最短的一条路径的端点都在起点集合中或者终点集合中。
所以要想办法把这些点多次分组把每种情况都能考虑到。考虑二进制分解,因为对于每两个不等的数,至少有一个二进制位不一样,这样我们可以枚举每一个二进制位,然后按0、1分成两组,就可以保证每两个点都分到过不同的集合中,然后跑最短路,答案肯定不会有遗漏,而且只需要分 logk 次。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int N=100010;
int first[N],to[N*2],nxt[N*2],w[N*2],tot;
int T,n,m,x,y,z,k,sp[N],num[N],dis[N],ans;
priority_queue<pair<int,int> > que;

void addedge(int x,int y,int z)
{
    tot++;
    nxt[tot]=first[x];
    first[x]=tot;
    to[tot]=y;
    w[tot]=z;
    tot++;
    nxt[tot]=first[y];
    first[y]=tot;
    to[tot]=x;
    w[tot]=z;
}

void getans()
{
    for(int t=0;(1<<t)<=k;++t)//枚举二进制位
    {
        memset(dis,0x3f3f3f3f,sizeof(dis));
        while(!que.empty()) que.pop();//因为dijkstra里有break,所以必须把que清空
        for(int i=1;i<=k;++i)
            if(num[sp[i]]&(1<<t))//按二进制位分组
            {
                dis[sp[i]]=0;//其实也没有超级起点,只是把所有的起点组里的dis都赋成0加入队列
                que.push(make_pair(0,sp[i]));
            }

        while(!que.empty())//dijkstra+优先队列
        {
            int u=que.top().second;
            que.pop();
            if(num[u]&&!(num[u]&(1<<t)))//最先在优先队列中找到的终点组里的点一定是和某个起点组的点距离最短的点,后面的dis都比它大,所以就不用继续了,更新一下ans继续分组
            {
                ans=min(ans,dis[u]);
                break;
            }

            for(int p=first[u];p;p=nxt[p])
            {
                int v=to[p];
                if(dis[v]>dis[u]+w[p])
                {
                    dis[v]=dis[u]+w[p];
                    que.push(make_pair(-dis[v],v));
                }
            }
        }
    }
}

int main()
{
    freopen("path.in","r",stdin);
    freopen("path.out","w",stdout);

    T=getint();
    while(T--)
    {
        tot=0,ans=0x3f3f3f3f;//清零操作
        memset(first,0,sizeof(first));
        memset(num,0,sizeof(num));

        n=getint(),m=getint();
        for(int i=1;i<=m;++i)
        {
            x=getint(),y=getint(),z=getint();
            addedge(x,y,z);
        }

        k=getint();
        for(int i=1;i<=k;++i)
            sp[i]=getint(),num[sp[i]]=i;//记录每一个可以选的点并按读取顺序给他们一个编号num

        getans();

        cout<<ans<<'\n';
    }
    return 0;
}

本题结。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值