挑战NPC

挑战NPC

时间限制: 1 Sec 内存限制: 256 MB
题目描述
传送门:http://uoj.ac/problem/171
ps.bzoj上的题面有毒,不用输出方案。

题解

神建图,orzvfk!!!
把每个筐子拆成三个点,将这三个点相互连边(其实只要连一条就可以)。对于每个球,向能与它连边的所有筐子的每一个点连边,然后跑最大匹配即可。
先简单证明一下:如果一个筐子只放一个球或不放球,那么代表这个筐子的三个点就必定会有两个点形成一组匹配。若一个筐子放球数超过1个,则显然无法形成匹配。
最大匹配怎么写?不是二分图,匈牙利、网络流就行不通了。
别急,我们还有带花树(orzorz)!
至于带花树怎么写,网上资料很多,本蒟蒻就不再赘述了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define N 610
#define M 200000
using namespace std;
int T,n,m,e,ans,fa[N],mate[N],flag[N],parent[N];
int k,la[N],ff[M],l,r,q[N],cnt,check[N];
struct node{int a,b;}map[M];

void add(int a,int b)
{
  map[++k]=(node){a,b};ff[k]=la[a];la[a]=k;
  map[++k]=(node){b,a};ff[k]=la[b];la[b]=k;
}

int find(int x)
{
  if(fa[x]==x)return x;
  return fa[x]=find(fa[x]);
}

int lca(int x,int y)
{
  x=find(x);y=find(y);cnt++;
  while(1)
  {
    if(check[x]==cnt)return x;
    if(x)check[x]=cnt,x=find(parent[mate[x]]);
    if(check[y]==cnt)return y;
    if(y)check[y]=cnt,y=find(parent[mate[y]]);  
  }
}

void merge(int x,int y,int t)
{
  while(find(x)!=t)
  {
    parent[x]=y;
    if(flag[mate[x]]==2)
      q[r]=mate[x],flag[q[r]]=1,r++;
    if(find(x)==x)fa[x]=t;
    if(find(mate[x])==mate[x])fa[mate[x]]=t;
    y=mate[x];x=parent[y];  
  }
}

int bfs(int S)
{
  for(int i=1;i<=n+m*3;i++)fa[i]=i,flag[i]=0;
  l=1;r=2;q[1]=S;flag[S]=1;parent[S]=0;
  while(l<r)
  {
    int x=q[l];l++;
    for(int a=la[x];a;a=ff[a])
    {
      int y=map[a].b;
      if(!flag[y])
      {
        parent[y]=x;flag[y]=2;
        if(!mate[y])
        {
          while(y)
          {
            int t=mate[parent[y]];
            mate[y]=parent[y];
            mate[parent[y]]=y;y=t;
          }
          return 1;
        }
        q[r]=mate[y];flag[q[r]]=1;r++;continue;
      }
      if(flag[y]==1&&find(x)!=find(y))
      {
        int t=lca(x,y);
        merge(x,y,t);merge(y,x,t);  
      }
    }
  }
  return 0;
}

int main()
{
  int a,b;
  scanf("%d",&T);
  while(T--)
  {
    scanf("%d%d%d",&n,&m,&e);ans=0;
    k=0;memset(la,0,sizeof(la));
    memset(parent,0,sizeof(parent));
    memset(mate,0,sizeof(mate));
    for(int i=1;i<=e;i++)
    {
      scanf("%d%d",&a,&b);
      add(a,n+b);add(a,n+m+b);add(a,n+m*2+b);
    }
    for(int i=1;i<=m;i++)add(n+i,n+m+i);
    for(int i=1;i<=n+m*3;i++)
      if(!mate[i])ans+=bfs(i);
    printf("%d\n",ans-n);
    for(int i=1;i<=n;i++)
    {
      mate[i]-=n;
      if(mate[i]%m)printf("%d ",mate[i]%m);
      else printf("%d ",m);
    }
    printf("\n");
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值