【BZOJ4405】[WC2016] 挑战NPC(带花树)

点此看题面

大致题意:\(n\)个球和\(m\)个筐,每个球可以放入某些筐中,且每个筐最多放\(3\)个球。现让你把所有球都放入筐中,问最多有多少个筐有不超过一个球。

前言

刚学带花树就来做这道题,不得不说实在是太妙了,根本想不到。。。

拆点

考虑我们把一个筐拆成三个点,然后在这三个点中两两连边,分别讨论一个筐中放不同数量球的情况:

  • 不放球:此时对答案贡献为\(1\),且一个筐拆成的三个点可以形成一个匹配。
  • \(1\)个球:此时对答案贡献为\(1\),且三个点中剩余的两个还可以形成一个匹配,共计两个匹配。
  • \(2\)个球:此时对答案贡献为\(0\),形成了两个匹配。
  • \(3\)个球:此时对答案贡献为\(0\),形成了三个匹配。

整理一下即可发现一个巧妙的规律:一个筐的贡献=形成匹配数-放的球数。

由于所有球都要放入筐中,因此放的球数之和就是\(n\)

那么我们只要最大化形成匹配数之和,也就是求一般图最大匹配,则上带花树即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 300
#define P 1000
#define E 30000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,et,ee,lnk[P+5];struct edge {int to,nxt;}e[2*(3*E+3*M)+5];
class FlowerMatchTree//带花树模板
{
	private:
		int s[P+5],l[P+5],c[P+5],vis[P+5];queue<int> q;
		int f[P+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
		I int LCA(RI x,RI y)
		{
			static int ti=0;++ti,x=fa(x),y=fa(y);
			W(vis[x]^ti) vis[x]=ti,x=fa(l[s[x]]),y&&(x^=y^=x^=y);return x;
		}
		I void Blossom(RI x,RI y,CI p)
		{
			W(fa(x)^p) l[x]=y,y=s[x],!f[x]&&(f[x]=p),
				!f[y]&&(f[y]=p),c[y]==2&&(q.push(y),c[y]=1),x=l[y];
		}
		I bool Match(CI x)//BFS
		{
			RI i;for(i=1;i<=n+3*m;++i) l[i]=c[i]=f[i]=0;W(!q.empty()) q.pop();
			RI k,p;q.push(x),c[x]=1;W(!q.empty()) for(i=lnk[k=q.front()],q.pop();i;i=e[i].nxt)
			{
				if(fa(k)==fa(e[i].to)||c[e[i].to]==2) continue;
				if(!c[e[i].to])
				{
					if(c[e[i].to]=2,l[e[i].to]=k,!s[e[i].to])
						{for(k=e[i].to;k;k=p) p=s[l[k]],s[k]=l[k],s[l[k]]=k;return 1;}
					c[s[e[i].to]]=1,q.push(s[e[i].to]);continue;
				}
				p=LCA(k,e[i].to),Blossom(k,e[i].to,p),Blossom(e[i].to,k,p); 
			}return 0;
		}
	public:
		I int operator [] (CI x) {return s[x];}
		I int GetAns()//求一般图最大匹配
		{
			RI i,t=0;for(i=1;i<=n+3*m;++i) s[i]=0;
			for(i=1;i<=n+3*m;++i) !s[i]&&Match(i)&&++t;return t;
		}
}T;
int main()
{
	RI Tt,i,x,y;scanf("%d",&Tt);W(Tt--)
	{
		#define P(i,j) (n+3*((i)-1)+(j))//拆点
		for(scanf("%d%d%d",&n,&m,&et),ee=0,i=1;i<=n+3*m;++i) lnk[i]=0;//清空
		for(i=1;i<=et;++i) scanf("%d%d",&x,&y),add(x,P(y,1)),//向筐拆成的三个点连边
			add(P(y,1),x),add(x,P(y,2)),add(P(y,2),x),add(x,P(y,3)),add(P(y,3),x);
		for(i=1;i<=m;++i) add(P(i,1),P(i,2)),add(P(i,2),P(i,1)),//一个筐拆成的点间两两连边
			add(P(i,1),P(i,3)),add(P(i,3),P(i,1)),add(P(i,2),P(i,3)),add(P(i,3),P(i,2));
		for(printf("%d\n",T.GetAns()-n),i=1;i<=n;++i) printf("%d%c",(T[i]-n+2)/3," \n"[i==n]);//输出答案
	}return 0;
}
posted @ 2020-06-11 14:29  TheLostWeak  阅读(13)  评论(0编辑  收藏