[WC2016]挑战NPC,一般图最大匹配+奇妙性质

正题

      题目链接

      这题一眼看上去很懵逼,看到要使<=1的最大,还以为是拆边网络流,结果发现不行。

      正解一般图最大匹配。

      我们把一个袋子拆成三个点的一个环。

      然后对于(v_i,u_i),我们从vi向ui对应的环的三个点连边。

      跑最大匹配Maxmatches,答案就是Maxmatches-n

证明

      首先我们可以保证每一个球都有一个匹配。

       因为我们可以从球开始找匹配,那么就肯定存在一个可行方案。

       接着,我们考虑每一个环能够承受多少个球。

       没有球的时候,这个篮子自己就会产生1的匹配。(因为与它相连的球不选这个环,那么这个环只能自己产生匹配。

       有1个球的时候,其中一个点会与球产生匹配,另外两个点有边相连,自己也会产生1的匹配。

       有2个球或者3个球的时候,自己本身不会产生匹配。

       那么刚好,多出来的匹配正是半空篮子的个数。

       证毕。

       在求最大匹配的时候,我们先保证那些求"有篮可放”,再使篮子尽量多的产生匹配。

       多出来的匹配正是Maxmatches-n.

#include
#include
#include
#include
#include
using namespace std;

int n,m,e;
int tot;
struct edge{
	int y,next;
}s[1000010];
int first[1010],match[1010],fa[1010],pre[1010],tp[1010],tf[1010],tim=0,len=0;
queue f;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
	s[++len]=(edge){x,first[y]};first[y]=len;
}

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

int Lca(int x,int y){
	for(tim++;;swap(x,y))
		if(x){
			x=findpa(x);
			if(tf[x]==tim) return x;
			tf[x]=tim;x=pre[match[x]];
		}
}

void make(int x,int y,int op){
	while(findpa(x)!=op){
		pre[x]=y;y=match[x];
		if(tp[y]==2) tp[y]=1,f.push(y);
		fa[x]=op;fa[y]=op;
		x=pre[y];
	}
}

bool Aug(int begin){
	for(int i=1;i<=tot;i++) fa[i]=i,tp[i]=pre[i]=0;
	while(!f.empty()) f.pop();
	f.push(begin);
	while(!f.empty()){
		int x=f.front();f.pop();
		for(int i=first[x];i!=0;i=s[i].next){
			int y=s[i].y;
			if(findpa(x)==findpa(y) || tp[y]==2) continue;
			if(!tp[y]){
				tp[y]=2;pre[y]=x;
				if(!match[y]){
					for(int last;x;x=pre[last],y=last){
						last=match[x];
						match[x]=y;match[y]=x;
					}
					return true;
				}
				tp[match[y]]=1;f.push(match[y]);
			}
			else{
				int op=Lca(x,y);
				make(x,y,op);make(y,x,op);
			}
		}
	}
	return false;
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		len=0;
		scanf("%d %d %d",&n,&m,&e);tot=n+3*m;
		for(int i=1;i<=tot;i++) first[i]=0,match[i]=0;
		int x,y;
		for(int i=1;i<=m;i++) ins(n+i,n+m+i),ins(n+m+i,n+m+m+i),ins(n+i,n+m+m+i);
		for(int i=1;i<=e;i++){
			scanf("%d %d",&x,&y);
			ins(x,n+y);ins(x,n+m+y);ins(x,n+m+m+y);
		}
		int ans=0;
		for(int i=1;i<=tot;i++) ans+=(!match[i] && Aug(i));
		printf("%d\n",ans-n);
		for(int i=1;i<=n;i++){
			if(match[i]<=n+m) printf("%d ",match[i]-n);
			else if(match[i]<=n+m+m) printf("%d ",match[i]-n-m);
			else printf("%d ",match[i]-n-m-m);
		}
		printf("\n");
	}
}

      发现原来的带花树打法不行,就是在找Lca的那里。一定要按照permui大佬的打法。

      另外,make函数中的继承父亲,我就直接继承了,好像还快了

你可能感兴趣的:([WC2016]挑战NPC,一般图最大匹配+奇妙性质)