题目链接:https://www.luogu.org/problemnew/show/P4258
题意:有n个球,m个筐子,一个筐子最多装三个球,有e个条件表示哪个小球可以装在哪个筐子里。每个球都必须进一个筐子,题目保证最少一个解是可以让每个球进筐子,且不超过三个球,问半空的筐子(不超过一个球的筐子)最多有多少个?
思路:将每个筐拆成三个点,分别代表三个空位,然后对于每个球可以放入框的对应关系,我们连三条边,再每个筐的三个点之间互相连边,跑一遍带花树即可。
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=650; bool g[maxn][maxn],inque[maxn],inpath[maxn]; bool inhua[maxn]; int st,ed,newbase,ans,n; int base[maxn],pre[maxn],match[maxn]; int head,tail,que[maxn]; int p,m,e,num[maxn]; void Push(int u) { que[tail]=u; tail++; inque[u]=1; } int Pop() { int res=que[head]; head++; return res; } int lca(int u,int v)//寻找公共花祖先 { memset(inpath,0,sizeof(inpath)); while(1) { u=base[u]; inpath[u]=1; if(u==st) break; u=pre[match[u]]; } while(1) { v=base[v]; if(inpath[v]) break; v=pre[match[v]]; } return v; } void reset(int u)//缩环 { int v; while(base[u]!=newbase) { v=match[u]; inhua[base[u]]=inhua[base[v]]=1; u=pre[v]; if(base[u]!=newbase) pre[u]=v; } } void contract(int u,int v)// { newbase=lca(u,v); memset(inhua,0,sizeof(inhua)); reset(u); reset(v); if(base[u]!=newbase) pre[u]=v; if(base[v]!=newbase) pre[v]=u; for(int i=1;i<=n;i++) { if(inhua[base[i]]){ base[i]=newbase; if(!inque[i]) Push(i); } } } void findaug() { memset(inque,0,sizeof(inque)); memset(pre,0,sizeof(pre)); for(int i=1;i<=n;i++)//并查集 base[i]=i; head=tail=1; Push(st); ed=0; while(head<tail) { int u=Pop(); for(int v=1;v<=n;v++) { if(g[u][v]&&(base[u]!=base[v])&&match[u]!=v) { if(v==st||(match[v]>0)&&pre[match[v]]>0)//成环 contract(u,v); else if(pre[v]==0) { pre[v]=u; if(match[v]>0) Push(match[v]); else//找到增广路 { ed=v; return ; } } } } } } void aug() { int u,v,w; u=ed; while(u>0) { v=pre[u]; w=match[v]; match[v]=u; match[u]=v; u=w; } } void edmonds()//匹配 { memset(match,0,sizeof(match)); for(int u=1;u<=n;u++) { if(match[u]==0) { st=u; findaug();//以st开始寻找增广路 if(ed>0) aug();//找到增广路 重新染色,反向 } } } //以上是带花树模板 void print() { ans=0; memset(num,0,sizeof(num)); for(int i=1;i<=p;i++) { //cout<<"***"<<i<<' '<<match[i]<<endl; match[i]=(match[i]-1-p)/3+1; num[match[i]]++; } for(int i=1;i<=m;i++) if(num[i]<=1)//半空筐子的数量 ans++; printf("%d ",ans); for(int u=1;u<=p;u++) printf("%d ",match[u]); } int main() { int t,u,v; scanf("%d",&t); while(t--) { memset(g,0,sizeof(g)); scanf("%d%d%d",&p,&m,&e); for(int i=0;i<e;i++)//建图 { scanf("%d%d",&u,&v); g[u][p+(v-1)*3+1]=g[p+(v-1)*3+1][u]=1; g[u][p+(v-1)*3+2]=g[p+(v-1)*3+2][u]=1; g[u][p+(v-1)*3+3]=g[p+(v-1)*3+3][u]=1; } for(int i=0;i<m;i++) { g[p+i*3+1][p+i*3+2]=g[p+i*3+2][p+i*3+1]=1; g[p+i*3+1][p+i*3+3]=g[p+i*3+3][p+i*3+1]=1; g[p+i*3+2][p+i*3+3]=g[p+i*3+3][p+i*3+2]=1; } n=p+m*3;//总共的点数。 edmonds(); print(); } return 0; }