hdu 3517 Adopt or not(二分图最大独立集关键点)

//hdu3517
//最大独立集
//此题求独立集中的关键点
//对于某点,如果未匹配(cx[i]==-1,cy[i]==-1),肯定在关键点中
//对于匹配中的点,若此点不是匹配中的关键点,那么删除此点最大匹配数不会改变,
//|V|减小,独立集变小,不能删。
//那么此点为独立集中的关键点
//(如果此点是二分图中的关键点,那么删除此点,最大匹配数减小,那么|V|-|M|增大,可以删。)
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 210;
const int INF=0x7fffffff;
#define clr(a) memset(a,0,sizeof(a));
int bmap[N][N];//每次记得清空
//对于两边各50000个点,200000条边的二分图最大匹配可以在1s内出解,效果很好:)
int X[N],Y[N];
struct HK{//用HK算法求解二分最大匹配问题(时间复杂度为sqrt(v)*e)
    int nx,ny,dis;
    int cx[N];//cx[i]表示左集合i顶点所匹配的右集合的顶点序号
    int cy[N]; //cy[i]表示右集合i顶点所匹配的左集合的顶点序号
    int dx[N];
    int dy[N];
    int Q[N];
    bool bmask[N];
    bool searchpath(){//极大最短增广路集
        int front,rear;
        front=rear=0;//初始化队列
        dis=INF;
        memset(dx, -1, sizeof(dx));
        memset(dy, -1, sizeof(dy));
        for(int i=1;i<=nx;i++){
            if(cx[i]==-1)//左边未匹配
            {
                Q[rear++]=i;//将左边节点放入队列
                dx[i]=0;//分层 所有潜在起点构成0层
            }
        }
        
        while(front<rear){
            int u=Q[front++];//u都是未匹配的
            if(dx[u]>dis)break;
            for(int v=1;v<=ny;v++){
                if(bmap[u][v]&&dy[v]==-1){//相邻未分层构成i+1层。
                    dy[v]=dx[u]+1;//v对应的距离 为u对应距离加1
                    if(cy[v]==-1)dis=dy[v];//右侧未匹配,更新最短距离
                    else{
                        dx[cy[v]]=dy[v]+1;//右侧已经匹配,根据已匹配边回溯。分层
                        Q[rear++]=cy[v];//左侧已匹配点入队。
                    }
                }
            }
        }
        return dis!=INF;
    }
    int findpath(int u){//只搜索极大最短增广路集中的边
        for(int v=1;v<=ny;v++){
            if(!bmask[v]&&bmap[u][v]&&dy[v]==dx[u]+1){//反向搜索的过程
                bmask[v]=1;
                if(cy[v]!=-1&&dy[v]==dis)continue;
                if(cy[v]==-1||findpath(cy[v])){
                    cy[v]=u;
                    cx[u]=v;
                    return 1;
                }
            }
        }
        return 0;
    }
    int Max_match(){
        int res=0;
        memset(cx,-1,sizeof(cx));
        memset(cy,-1,sizeof(cy));
        while(searchpath()){
            memset(bmask, 0, sizeof(bmask));
            for(int i=1;i<=nx;i++){
                if(cx[i]==-1){
                    res+=findpath(i);
                }
            }
        }
        return res;
    }
};
HK hk;
//调用的时候确定hk.nx,hk.ny,然后直接调用Max_match()就行
struct P{
    int like;
    int disnum;
    int dis[N];
}P[N];
int a,b;
void input(){
    int x,n;
    scanf("%d%d%d",&a,&b,&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        if(x<=a){//只会反对对方党派的,而且多个人支持相同的,相当于满意的人数累加。拆点
            X[++hk.nx]=i;
        }
        else{
            Y[++hk.ny]=i;
        }
        P[i].like=x;
        scanf("%d",&P[i].disnum);
        for(int j=0;j<P[i].disnum;j++){
            scanf("%d",&P[i].dis[j]);
        }
    }
    for(int i=1;i<=hk.nx;i++){
        for(int j=1;j<=hk.ny;j++){
            int L=X[i],R=Y[j];
            for(int k=0;k<P[R].disnum;k++){
                if(P[L].like==P[R].dis[k]){
                    bmap[i][j]=1;break;
                }
            }
            if(!bmap[i][j]){
                for(int k=0;k<P[L].disnum;k++){
                    if(P[R].like==P[L].dis[k]){
                        bmap[i][j]=1;break;
                    }
                }
            }
        }
    }
}
int used[N];
int pointX[N],pointY[N];
int delX,delY;
bool findY(int u){
    for(int v=1;v<=hk.ny;v++){
        if(v!=delY&&bmap[u][v]&&!used[v]){
            used[v]=1;
            if(hk.cy[v]==-1||findY(hk.cy[v])){
                return 1;
            }
        }
    }
    return 0;
}
bool findX(int v){
    for(int u=1;u<=hk.nx;u++){
        if(u!=delX&&bmap[u][v]&&!used[u]){
            used[u]=1;
            if(hk.cx[u]==-1||findX(hk.cx[u])){
                return 1;
            }
        }
    }
    return 0;
}

void solve(){
    for(int i=1;i<=hk.nx;i++){
        if(hk.cx[i]==-1){
            pointX[i]=1;
            continue;//关键点在一定在匹配M中
        }
        delY=hk.cx[i];
        clr(used);
        if(findY(i))pointY[delY]=1;
    }
    for(int i=1;i<=hk.ny;i++){
        if(hk.cy[i]==-1){
            pointY[i]=1;
            continue;
        }
        delX=hk.cy[i];
        clr(used);
        if(findX(i))pointX[delX]=1;
    }
}
int pro[N];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        memset(bmap,0,sizeof(bmap));
        clr(pro);
        hk.nx=hk.ny=0;
        clr(pointX);clr(pointY);
        input();//建图
        int ans=hk.Max_match();
        solve();
        printf("%d\n",hk.nx+hk.ny-ans);
        int cnt=0;
        for(int i=1;i<=hk.nx;i++){
            if(pointX[i]==1){
                pro[P[X[i]].like]=1;
            }
        }
        for(int i=1;i<=hk.ny;i++){
            if(pointY[i]==1){
                pro[P[Y[i]].like]=1;
            }
        }
        for(int i=1;i<=a+b;i++){
            if(pro[i]==1)++cnt;
        }
        printf("%d",cnt);
        for(int i=1;i<=a+b;i++){
            if(pro[i]==1)printf(" %d",i);
        }
        printf("\n");
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值