HDU 6166 Senior Pan (思维枚举+最短路 求最近点对)

来源:互联网 发布:最好的文档扫描软件 编辑:程序博客网 时间:2024/06/06 17:52


Senior Pan

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 898    Accepted Submission(s): 361


Problem Description
Senior Pan fails in his discrete math exam again. So he asks Master ZKC to give him graph theory problems everyday.
The task is simple : ZKC will give Pan a directed graph every time, and selects some nodes from that graph, you can calculate the minimum distance of every pair of nodes chosen in these nodes and now ZKC only cares about the minimum among them. That is still too hard for poor Pan, so he asks you for help.
 

Input
The first line contains one integer T, represents the number of Test Cases.1≤T≤5.Then T Test Cases, for each Test Cases, the first line contains two integers n,m representing the number of nodes and the number of edges.1≤n,m≤100000
Then m lines follow. Each line contains three integers xi,yi representing an edge, and vi representing its length.1≤xi,yi≤n,1≤vi≤100000
Then one line contains one integer K, the number of nodes that Master Dong selects out.1≤K≤n
The following line contains K unique integers ai, the nodes that Master Dong selects out.1≤ai≤n,ai!=aj
 

Output
For every Test Case, output one integer: the answer
 

Sample Input
15 61 2 12 3 33 1 32 5 12 4 24 3 131 3 5
 

Sample Output
Case #1: 2
 

Source
2017 Multi-University Training Contest - Team 9
 

Recommend
liuyiding   |   We have carefully selected several similar problems for you:  6181 6180 6179 6178 6177 

题意:给你一个n个点m条边的有向带边权的图。现在给你k个点,问k个点中最近的点对距离。 n, m, k <= 1e5

思路:

考虑到spfa可以求两个集合间的最短路径,(即初始将某个集合所有点入堆,dis = 0,直到找到另一个集合的第一个点结束)。

想到了这个问题我们就来想一下怎么去划分集合了,因为任意两点都要求一次,所以划分集合的方法必须要保证每两个点都至少一次被分到了不同的集合里去.

这里比较巧妙的一个方法就是 因为每个点都不相同,所以他们点的二进制状态中至少有一位不相同,那么我们就可以枚举二进制中的每一位,相同的分到一个集合,然后跑一次spfa维护最小值即可.因为n为1e5,所以最多17次就可以结束了.

另外一个需要注意的问题就是,该图是一个有向图,所以划分完集合后我们不确定是1集合到2集合最短,还是2集合到1集合最短,所以我们要正反跑两次spfa,一次1到2,一次2到1.维护一个最小值,就是答案了.

代码:

#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>#include <queue>#include <vector>using namespace std;typedef long long ll;const ll INF = 1e18;const int maxn = 100005;int n, m, a[maxn], book[maxn], target[maxn];ll dis[maxn];struct node{    int to;    ll w;    node(){}    node(int tt, ll ww) : to(tt), w(ww){}};queue<int> q;void init(){    memset(target, 0, sizeof(target));    memset(book, 0, sizeof(book));    for(int i = 1; i <= n; i++) dis[i] = INF;    while(!q.empty()) q.pop();}vector<node> v[maxn];ll spfa(){    while(!q.empty())    {        int u = q.front(); q.pop();        book[u] = 0;        for(int i = 0; i < v[u].size(); i++)        {            int to = v[u][i].to;            int w = v[u][i].w;            if(dis[u]+w <= dis[to])            {                dis[to] = dis[u]+w;                if(!book[to])                {                    book[to] = 1;                    q.push(to);                }            }        }    }    ll ans = INF;    for(int i = 1; i <= n; i++)        if(target[i])            ans = min(ans, dis[i]);    return ans;}int main(){    int t, ca = 1;    cin >> t;    while(t--)    {        scanf("%d%d", &n, &m);        for(int i = 0; i <= n; i++) v[i].clear();        int x, y, z, k;        for(int i = 1; i <= m; i++)            scanf("%d%d%d", &x, &y, &z), v[x].push_back(node(y, z));        scanf("%d", &k);        for(int i = 1; i <= k; i++)            scanf("%d", &a[i]);        ll ans = INF;        for(int i = 0; i <= 20; i++)        {            init();            for(int j = 1; j <= k; j++)            {                if(a[j]&(1<<i))                  dis[a[j]] = 0, q.push(a[j]), book[a[j]] = 1;                else                  target[a[j]] = 1;            }            ans = min(ans, spfa());            init();            for(int j = 1; j <= k; j++)            {                if(a[j]&(1<<i))                 target[a[j]] = 1;                else                  dis[a[j]] = 0, q.push(a[j]), book[a[j]] = 1;            }            ans = min(ans, spfa());        }        printf("Case #%d: %lld\n",ca++,ans);    }    return 0;}

还可以随机划分集合, 转自点击打开链接

思路:


①我们在一个集合中找两个点显然有些难度,我们假设有一个起点集合,一个终点集合的话,我们建立一个超级源点连入起点集合的各个点,然后再建立一个超级汇点,将终点集合的各个点连入这个超级汇点的话,我们跑一遍从超级源点到超级汇点的最短路就很简单能够解决问题。


②但是我们现在没有这两个集合,我们不妨考虑将关键点集合分出两个集合,那么我们怎样分能够使得出错率最小呢?我们显然希望对半分。

所以我们随机这K个关键点的排列顺序,然后前半部分作为起点集合,后半部分作为终点集合去跑就行了。


③随机一次出来的结果出错率为3/4(正确的概率:起点分布正确的概率是1/2.终点分布正确的概率是1/2.相乘为1/4).两次都出错的概率为3/4*3/4.三次都出错的概率为3/4*3/4*3/4.依次类推,显然随机的次数越多,正确结果的概率越大。我们只需要其中任意一次正确即可。所以这样做的正确率是可以保证的。


所以我们随机20次左右就足够了,过程跑SPFA,维护最小解即可。


#include<stdio.h>  #include<string.h>  #include<queue>  #include<algorithm>  using namespace std;  #define ll __int64  struct node  {      ll from,to,next,w;  } e[150000];  ll cont,n,m,K,ss,tt,ans;  ll xx[150000];  ll yy[150000];  ll ww[150000];  ll ned[150000];  ll head[150000];  ll dist[150000];  ll vis[150000];  void add(ll from,ll to,ll w)  {      e[cont].w=w;      e[cont].to=to;      e[cont].next=head[from];      head[from]=cont++;  }  void SPFA()  {      queue<ll>s;      memset(vis,0,sizeof(vis));      for(ll i=1; i<=tt; i++)dist[i]=0x3f3f3f3f;      dist[ss]=0;      s.push(ss);      while(!s.empty())      {          ll u=s.front();          s.pop();          vis[u]=0;          for(ll i=head[u];i!=-1;i=e[i].next)          {              ll v=e[i].to;              ll w=e[i].w;              if(dist[v]>dist[u]+w)              {                  dist[v]=dist[u]+w;                  if(vis[v]==0)                  {                      vis[v]=1;                      s.push(v);                  }              }          }      }      ans=min(ans,dist[tt]);  }  void Slove()  {      ans=0x3f3f3f3f;      ll temp=20;      while(temp--)      {          ss=n+1;          tt=n+2;          cont=0;          memset(head,-1,sizeof(head));          random_shuffle(ned,ned+K);          for(ll i=1; i<m; i++)          {              add(xx[i],yy[i],ww[i]);          }          for(ll i=0; i<K/2; i++)          {                  add(ss,ned[i],0);          }          for(ll i=K/2;i<K;i++)          {              add(ned[i],tt,0);          }          SPFA();      }      printf("%I64d\n",ans);  }  int main()  {      ll kase=0;      ll t;      scanf("%I64d",&t);      while(t--)      {          scanf("%I64d%I64d",&n,&m);          cont=0;          memset(head,-1,sizeof(head));          for(ll i=1; i<=m; i++)          {              scanf("%I64d%I64d%I64d",&xx[i],&yy[i],&ww[i]);          }          scanf("%I64d",&K);          for(ll i=0; i<K; i++)scanf("%I64d",&ned[i]);          printf("Case #%I64d: ",++kase);          Slove();      }  }  


原创粉丝点击