hdu 5899 oasis in desert(acm/icpc 沈阳赛区网络赛,Floyd+二分图判定+最大匹配,好难啊)

探讨了如何通过构建二分图找到同时满足最大危险集和最小安全集条件的点集,利用Floyd算法计算最短路径,通过DFS确定二分图属性,并采用匈牙利算法实现最大匹配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门: http://acm.hdu.edu.cn/showproblem.php?pid=5899

题目大意:

沙漠中有N个绿洲,由M条路相连,每条路的长度为L。

定义两个集合:
(1)最大危险集:对于集合中任意两点,其最小距离大于k,满足此条件的集合有多个,取最大的那个。
(2)最小安全集:对于图中任意两点,只要他俩的距离≤k,那么最小安全集中至少包含这两个点中的一个。满足条件的集合有多个,取最小的那个。

问这个图中存不存在一个点集,它既是最大危险集,又是最小安全集。
如果存在,输出字典序最小的那个集合,如果不存在,输出Impossible。

题目分析:
其实拿到这个题,想到了跟二分图匹配有关系,但一开始是把原图的点拆成两个,怎么想也对不上。看了这位大神http://blog.youkuaiyun.com/wmdcstdio/article/details/52576840的题解就明白了。
这道题应该这样构图:

构建图 G(V,E) ,其中 E={(i,j)|dist(i,j)k,(i,j)E} .也就是把原图里最短距离≤k的两个边连起来。
接下来是见证奇迹的时刻~~

我们假设一个点v1属于答案集,那么:
1. v1点相邻的点一定不属于答案集;(因为最大危险集定义)
2. 和1中相邻的点一定属于答案集;(因为最小安全集定义)
3. 和2中相邻的点一定不属于答案集;
….

因此得出结论:
1.图 G 是个二分图.
2.“最大危险集”即为图 G 的最大独立集(因为要满足任意两点间距离>k,也就是无边);
3.“最小安全集”即为图 G 的最小支配集(因为这个集合能“支配”所有小于等于L的路径,也就是 G 的边嘛)

我们都知道,最大独立集+最小支配集=图的总点数,所以说,答案集存在只需最大匹配=n/2即可。

这里面还有一个问题,我也在这里wa了六七发。那就是输入的原图有可能不连通,这种情况要输出impossible。上述博主在这里似乎有点问题,因为如果不连通,就算每个子图都存在这种答案集,如果取了并集也会存在两个点他们是连不通的,而“最小距离大于k”的前提是存在最小距离,他俩都连不通,就没有意义。所以还要加一条判断就是,图G是连通的!!!

求最小距离用了Floyd算法,判定二分就是裸的dfs,然后求最大匹配用了一个匈牙利算法。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
int t,n,m,k,a,b,l;

int p[510];//记录每个点的颜色,0没染,1黑色,2白色
int dis[510][510];//因为要算距离,所以用邻接矩阵存图
vector<int> g[510];
int f[510],vis[510];
int cnt;
void floyd() {
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

}
void initGraph() {
  //构造图g,当i-j边距离小于等于k的时候,给i-j加一条边
    for(int i=1;i<=n;i++) {
        for(int j=i+1;j<=n;j++) {
            if(dis[i][j]<=k) {
                g[i].push_back(j);
                g[j].push_back(i);
            }
        }
    }
}
bool dfs(int u,int col) { //给i点染col色
    //cout<<u<<","<<col<<endl;
    p[u]=col;

    for(int i=0;i<g[u].size();i++) {
        int v=g[u][i];
        if(p[v]==col)//如果相邻的两个点颜色一样,那就是非法的
            return false;
        else if(p[v]==0) {
            if(col==1) {
                if(!dfs(v,2)) return false;
            }
            else {
                if(!dfs(v,1)) return false;
            }
        }
    }
    return true;
}
bool paint() {
    //v1.clear();v2.clear();
    for(int i=1;i<=n;i++) {
        if(p[i]==0) {
            p[i]=1;
            if(!dfs(i,1))
                return false;
        }
    }
    return true;
}
bool dfs2(int i) {
    vis[i]=1;
    for(int j=0;j<g[i].size();j++) {
        int u=g[i][j];
        if(f[u] == -1 || (!vis[f[u]] && dfs2(f[u]))) {
            f[u]=i;
            f[i]=u;
            return 1;
        }
    }
    return 0;
}
bool match() {
    memset(f,-1,sizeof(f));
    for(int i=1;i<=n;i++) {
        if(f[i]==-1) {
            memset(vis,0,sizeof(vis));
            cnt+=dfs2(i);
        }
    }
    return cnt*2==n;
}
void solve() {
    int con=1;
    for(int i=1;i<=n;i++)
        if(dis[1][i]<INF)
            con++;
    if(!paint() || con<n || !match())
        printf("Impossible\n");
    else {
        vector<int> ans;
        for(int i=1;i<=n;i++)
            if(p[i]==1)
                ans.push_back(i);
        printf("%d\n", ans.size());
        for(int i=0;i<ans.size()-1;i++)
            printf("%d ", ans[i]);
        printf("%d\n", ans.back());
    }
}
int main() {
    scanf("%d",&t);
    while(t--) {
        scanf("%d %d %d",&n,&m,&k);
        memset(dis,0x3f,sizeof(dis));
        for(int i=1;i<=n;i++)
            dis[i][i]=0;
        memset(p,0,sizeof(p));
        memset(g,0,sizeof(g));
        cnt=0;
        while(m--) {
            scanf("%d %d %d",&a,&b,&l);
            dis[a][b]=l;dis[b][a]=l;
        }
        if(n==0) { //特判0个点
            printf("0\n");
            continue;
        }

        floyd();
        initGraph();
        solve();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值