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

42 篇文章 0 订阅
16 篇文章 1 订阅


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
  
  
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
 

Sample Output
  
  
Case #1: 2
 

Source
 

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();  
    }  
}  


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值