2015年ACM/ICPC沈阳赛区 M题(思维+堆优化的Dijkstra算法)

题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5262

题意:

给你n(n<=1e5)个点,m个完全图集合(每个集合包含权值v,集合中的点数cnt和集合里的点的标号,表示集合里的点构成无向完全图,两点之间权值为v(时间))。

保证集合中所有的点的数量不超过1e6。

两个人分别从1点和n点走,只可以在一个点相遇(一个人可以等另一个人),求最短相遇时间和最短时间的前提下在哪些编号的点相遇时间都是最短。

如果无法相遇,输出“Evil John”。

思路:

比赛的时候没做出来,不过看了题解之后,感觉构图真是巧妙。可能很简单吧,比赛的时候过了好多人,但是我却没想到。。。

考虑题目中特殊条件“保证集合中所有的点的数量不超过1e6。”,显然不能每个集合每个点相互建边。因此要将集合建边。

但是我们要求的是1点和n点的单源最短路 怎么能用集合建图呢???

假设我们现在从1点开始走,如果想走到另一个点,只能走集合。于是,我们把集合编号 n+1~n+m+1。

对于集合中每个点 向集合建边 add(x,n+i,v);add(n+i,x,0)。这样只要一个非这个集合中的点想经过集合中的点,就必定要经过这条边,即花费时间。进入这个集合后,走向中集合中的任何点花费就都为0了(因为进来的点已经加了花费,走任何点都相当于直接走过去,花费就是v)。这样经过这个集合时时间就仅增加了v。

然后套个快一点的堆优化的Dijkstra算法板子分别求两次单源最短路,然后枚举每个相遇的点,取个最小值,对于相遇时间等于最小值的点就可以直接输出了。

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3fLL
#define pa pair<int,int>
using namespace std;
const int maxn = 2200010;
struct node
{
    int to,next;
    ll v;
};
struct Dijkstra
{
  node edge[maxn];
  int cnt,head[maxn],n;
  ll dis[maxn];
  void init(int nn)
  {
      n=nn;
      cnt=0;
      for(int i=0;i<=n;i++) head[i]=0;
  }
  void add(int u,int v,int w)
  {
    cnt++;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
    edge[cnt].v=w;
  }

  void dijkstra(int s)
  {
    priority_queue<pa,vector<pa>,greater<pa> >q;
    int i,now;
    for (i=1;i<=n;i++)
    dis[i]=inf;
    dis[s]=0;
    q.push(make_pair(0,s));
    while (!q.empty())
      {
        now=q.top().second;
        q.pop();
        for (i=head[now];i;i=edge[i].next)
          if (dis[now]+edge[i].v<dis[edge[i].to])
            {
              dis[edge[i].to]=dis[now]+edge[i].v;
              q.push(make_pair(dis[edge[i].to],edge[i].to));
            }
      }
  }
}D1,DN;
int n,m;
ll v;
int main()
{
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        D1.init(n+m+1);
        DN.init(n+m+1);
        for(int i=0;i<m;i++)
        {
            int cnt;
            scanf("%lld%d",&v,&cnt);
            for(int j=0;j<cnt;j++)
            {
                int x;
                scanf("%d",&x);
                D1.add(x,n+i+1,v);
                D1.add(n+i+1,x,0);
                DN.add(x,n+i+1,v);
                DN.add(n+i+1,x,0);
            }
        }
        D1.dijkstra(1);
        DN.dijkstra(n);
        ll mi=inf;
        for(int i=1;i<=n;i++)
        {
            D1.dis[i]=max(D1.dis[i],DN.dis[i]);
            mi=min(mi,D1.dis[i]);
        }
        printf("Case #%d: ",cas++);
        if(mi==inf) puts("Evil John");
        else
        {
            printf("%d\n",mi);
            bool fg=0;
            for(int i=1;i<=n;i++)
            if(mi==D1.dis[i]){
                if(!fg) printf("%d",i),fg=1;
                else printf(" %d",i);
            }
            puts("");
        }
    }
    return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值