HDOJ 3585 maximum shortest distance (二分+最大团)


点击打开链接


题意:从n个点中任取k个点,求这k个点两两之间最小距离的最大值。


解析:先把n个点之间的不同距离保存下来,从小到大排序后二分距离,比该距离小的边当作不存在,然后把剩余的边求最大团。如果最大团顶点数大于等于k,就向上缩小区间,否则向下缩小区间。(首先可以肯定的是该问题一定有解,就是二分一定能得到一个最佳距离。等于k的时候还需要向上缩小区间是为了得到最大值。)


最大团采用的是BK算法


朴素版本:

BronKerbosch(All, Some, None):
if Some and None are both empty:
report All as a maximal clique
for each vertex v in Some:
BronKerbosch1(All ⋃ {v}, Some ⋂ N(v), None ⋂ N(v))
Some := Some \ {v}
None := None ⋃ {v}

优化版本:

BronKerbosch(All, Some, None):
if Some and None are both empty:
report All as a maximal clique
choose a pivot vertex u in Some ⋃ None
for each vertex v in Some \ N(u)::
BronKerbosch1(All ⋃ {v}, Some ⋂ N(v), None ⋂ N(v))
Some := Some \ {v}
None := None ⋃ {v}

all是当前已经被选的顶点集,some是客观上可以选主观上也打算选的顶点集,none是客观上可以选主观上不打算选的顶点集。n(v)是v的邻接点。

只有当some和none都为空的时候才是一个极大团,这时候保证some中没有顶点可以再加入all,并且none中如果有点,那么它客观上其实也是能加入到all的,所以none必须也为空。

每一个在some中的点v,都有加入all和不加入all两种选择,如果加入all,那么除了all改变,some和none由于下一次只能取和v邻接的点,取了n(v)的交集。

接下来就是不选的情况。

优化版本可以避免把some中所有的顶点全部循环一遍,任意一个点u,如果最大团中既不包括u又不包括u的非邻接点,那么一定可以把u加入到最大团中,这样就和假设矛盾,所以一定在两者之间包括一个,不可能都包括。在后面的模板中,让u从some中取,让u等于some中的第一个点,i=0的时候就是取u的情况,i>0的时候是取u的非邻接点的情况。当然如果u从none中取也可以,只要改成一开始u=none[d][0]。只能取u的非邻接点,u从none中来所以不能给all。但是这样也起到剪枝的效果。

还有一个问题就是,有没有可能all里面就有u的非邻接点呢?不可能,因为如果有,那么some里面不可能出现u了。


void dfs(int d, int an, int sn, int nn)
{
    if(sn == 0 && nn == 0) ++ S; //得到的是极大团,最大团是极大团里面顶点数最多的
    int u = some[d][0];//pivot vertex
    for(int i = 0; i < sn; i ++)
    {
        int v = some[d][i];
        if(g[u][v]) continue;
        int tsn = 0, tnn = 0;
        for(int j = 0; j < an; j ++) all[d + 1][j] = all[d][j];
        all[d + 1][an] = v;
        for(int j = 0; j < sn; j ++)if(g[v][some[d][j]]) some[d + 1][tsn ++] = some[d][j];
        for(int j = 0; j < nn; j ++) if(g[v][none[d][j]]) none[d + 1][tnn ++] = none[d][j];
        dfs(d + 1, an + 1, tsn, tnn);
        //把v从some取出,放入none
        some[d][i] = 0, none[d][nn ++] = v;
    }
}





#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 500
using namespace std;
int n, k, cnt, l, r, mid, le;
double ans;
int x[N], y[N], d[N][N], dist[1000000], a[N], some[N][N], all[N][N], none[N][N];
int getdist(int u, int v)
{
    return (x[u] - x[v]) * (x[u] - x[v]) + (y[u] - y[v]) * (y[u] - y[v]);
}
int dfs(int deep, int an, int sn, int nn)
{
    if (an >= k) return 1;
    if (an + sn < k) return 0;
    int u = some[deep][1];
    for (int i = 1; i <= sn; i++)
    {
        int v = some[deep][i];
        int tsn = 0, tnn = 0;
        if (d[u][v] >= le) continue;
        for (int j = 1; j <= an; j++) all[deep+1][j] = all[deep][j];
        all[deep+1][an+1] = v;
        for (int j = 1; j <= sn; j++)
            if (d[v][some[deep][j]] >= le) some[deep+1][++tsn] = some[deep][j];
        for (int j = 1; j <= nn; j++)
            if (d[v][none[deep][j]] >= le) none[deep+1][++tnn] = none[deep][j];
        if (dfs(deep+1, an+1, tsn, tnn)) return 1; // 这里要return 1;
        some[deep][i] = 0;
        none[deep][++nn] = v;
    }
    return 0;
}
int main()
{
    while(~scanf("%d%d", &n, &k))
    {
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &x[i], &y[i]);
        cnt = 0;
        memset(d, 0, sizeof(d));
        for (int i = 1; i < n; i++)
            for (int j = i+1; j <= n; j++)
        {
            d[i][j] = getdist(i, j);
            d[j][i] = d[i][j];
            cnt++;
            dist[cnt] = d[i][j];
        }
        sort(dist+1, dist+cnt+1);
        l = 1;
        r = cnt;
        while (l <= r)
        {
            mid = (l + r) / 2;
            le = dist[mid];
            a[0] = 0;
            for (int i = 1; i <= n; i++) some[1][i] = i;
            if (dfs(1, 0, n, 0))
            {
               l = mid + 1;
               ans = sqrt(dist[mid]);
            }
            else
            {
               r = mid - 1;
            }
        }
        printf("%.2f\n", ans);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值