2020牛客多校第一场I题1 or 2(一般图匹配)

题意:给你n个点m条边以及n个点的度(只为1或者2),选择一些边满足点的度要求,若能满足输出YES,反之NO;

题解:我们把点和边都进行拆分

例子:

3 2
1 1 2
1 3
2 3

 

点按照度,边拆分成两个点,跑一遍一般图的最大匹配,若能完美匹配就是YES

完美匹配就是所有点都能匹配 通俗点就是所有点都是一对一对的

 

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 507;
const int M = 507*507*2;
struct node{int to,nxt;}g[M];
int head[N],cnt;
int vis[N],match[N],f[N],pre[N],Id,id[N];
int dd[N];
int nodes[N][N];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点 
//id: 找LCA用 
int n,m,ans,u,v,tot;
queue<int> q;
void Init(){
    Id=ans=cnt=tot=0;
    memset(nodes,0,sizeof(nodes));
    memset(head,-1,sizeof(head));
    memset(id,0,sizeof(id));
    memset(match,0,sizeof(match));
    memset(dd,0,sizeof(dd));    
//    for(int i=1;i<=n;i++)
//        head[i]=-1,id[i]=match[i]=dd[i]=0;
}
void add(int u,int v){ 
    g[cnt]=node{v,head[u]},head[u]=cnt++;
}
int getf(int x){ 
    return f[x]==x?x:f[x]=getf(f[x]); 
}
//查询x和y在带花树中的LCA 
int LCA(int x,int y){
    //沿着增广路向上找lca 
    for(++Id;;swap(x,y))//x,y交替向上 
        if(x)
        {
            x=getf(x);//有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点 
            if(id[x]==Id) return x; //x,y在同一环中,一定会找到已被编号的点,该点即为LCA。 
            else id[x]=Id,x=pre[match[x]];//给点编号,并沿着非匹配边向上找        
        }
}
//缩点(开花),将x、y到LCA(l)路径中的点,缩为一点 
void blossom(int x,int y,int l){ 
    while(getf(x)!=l){
        //增广路取反 
        pre[x]=y,y=match[x];
        //如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点    
        if(vis[y]==2) vis[y]=1,q.push(y);
        //只改变是根的点 
        if(getf(x)==x) f[x]=l;
        if(getf(y)==y) f[y]=l;
        //增广路取反 
        x=pre[y];
    } 
}
bool aug(int s){
    //puts("s");
    //每次都以s为起点bfs,建带花树 
    for(int i=1;i<=n;i++)
        vis[i]=pre[i]=0,f[i]=i;    
    while(!q.empty()) q.pop();
    
    q.push(s),vis[s]=1;
    while(!q.empty()){
        u=q.front();q.pop();
        for(int i=head[u];~i;i=g[i].nxt){
            v=g[i].to;
            //如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),只接跳过 
            //这种情况不会增加匹配数 
            if(getf(u)==getf(v)||vis[v]==2) continue;
            //如果没有被染色 
            if(!vis[v])    {
                //先染为白色,将前继点指向u 
                vis[v]=2,pre[v]=u;
                //如果没有被匹配过,直接匹配成功 
                if(!match[v]){
                    //增广路取反 
                    for(int x=v,last;x;x=last)
                        last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;        
                    return 1;
                }
                //如果被匹配过,则把匹配v的点染为黑色,放入队列中    
                vis[match[v]]=1,q.push(match[v]);
            }
            //v是黑色,形成奇环,则缩点(开花)。 
            else {
                 int lca=LCA(u,v);
                blossom(u,v,lca),blossom(v,u,lca);
            }
        }    
    }
    return 0;
}
int main(){
    while(~scanf("%d%d",&n,&m))    {
        Init();
        for(int i=1;i<=n;i++){
            scanf("%d",&dd[i]);
            for(int j=1;j<=dd[i];j++){
                nodes[i][j-1]=++tot;
            }
        }
        
        for(int i=1;i<=m;i++){
            scanf("%d%d",&u,&v);
            ++tot;
            for(int j=1;j<=dd[u];j++){
                add(tot,nodes[u][j-1]),add(nodes[u][j-1],tot);
            }
            ++tot;
            for(int j=1;j<=dd[v];j++){
                add(tot,nodes[v][j-1]),add(nodes[v][j-1],tot);
            }
            add(tot,tot-1);
            add(tot-1,tot);
        }
        n=tot; 
//        cout<<n<<" "<<tot<<endl;
        for(int i=1;i<=n;i++)
            if(!match[i]&&aug(i))
                ans++;    
//        puts("!");
        ll counter=0;
        for(int i=1;i<=n;i++){
            if(match[i]) ++counter;
        }
        if(counter==tot)printf("Yes\n");
          else printf("No\n");
//        printf("%d\n",ans);
//        for(int i=1;i<=n;i++)
//            printf("%d%c",match[i]," \n"[i==n]);
    }
    return 0;    
}
View Code

 

posted @ 2020-07-31 23:17  杰瑞与汤姆  阅读(296)  评论(1编辑  收藏  举报