P4258 [WC2016]挑战NPC(一般图匹配)

P4258 [WC2016]挑战NPC(一般图匹配),第1张

P4258 [WC2016]挑战NPC(一般图匹配) P4258 [WC2016]挑战NPC(一般图匹配)

很妙,一个筐最多装3个球,显然是拆点,但是贡献只有半筐(球数小于2)。

考虑建图的时候,把三个筐点相互连边。

这样筐0个球,内部产生一个匹配,贡献为1。实际贡献为1

1个球,内部产生一个匹配,贡献为2。实际贡献为1

2个球,内部产生0个匹配,贡献为2。实际贡献为0

3个球,内部产生0个匹配,贡献为3.实际贡献为0.

观察发现,贡献 - 匹配的球数 = 实际贡献。

这样跑一遍一般图匹配,然后减去总球数 n n n即可。

输出注意还原点的编号。

// Problem: P4258 [WC2016]挑战NPC
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4258
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Date: 2021-11-23 14:38:06
// --------by Herio--------

#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=2e4+5,M=505,inf=0x3f3f3f3f,mod=1e9+7;
const int hashmod[4] = {402653189,805306457,1610612741,998244353};
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair
#define PLL pair
#define x first
#define y second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr) 
void Print(int *a,int n){
	for(int i=1;i		//x=max(x,y)  x=min(x,y)
void cmx(T &x,T y){
	if(x
void cmn(T &x,T y){
	if(x>y) x=y;
}
struct Blossom_Tree{
    vectore[N];
    queueq;
    int mh[N],pre[N],s[N],id[N],vis[N],n,num;
    void init(int nn){	//初始化 
        n=nn,num=0;
        for(int i=1;i<=n;i++) e[i].clear(),mh[i]=pre[i]=vis[i]=s[i]=id[i]=0;
    }
    void add(int u,int v){
        e[u].push_back(v),e[v].push_back(u);
    }
    int find(int x){ return s[x]==x?x:s[x]=find(s[x]);}
    int LCA(int x,int y){	//求LCA. 
    for(++num;;swap(x,y))
        if(x){
        if(id[x=find(x)]==num) return x;//在同一个奇环肯定能找到被编号的结点. 
        id[x]=num,x=pre[mh[x]];	//沿增广路径的非匹配边向上走直到找到祖先. 
    }
    }
    void blossom(int x,int y,int rt){	//缩点(开花)  O(n)
        while(find(x)!=rt){
            pre[x]=y,y=mh[x];
            if(vis[y]==2) vis[y]=1,q.push(y);//如果奇环上有2型结点 就变为1型结点加入队列. 
            if(find(x)==x) s[x]=rt;//只对根处理. 
            if(find(y)==y) s[y]=rt;
            x=pre[y];
        }
    }
    bool aug(int st){	//求增广路径.O(n) 
    for(int i=1;i<=n;i++) vis[i]=pre[i]=0,s[i]=i; 
    while(!q.empty()) q.pop();
    q.push(st),vis[st]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(auto v:e[u]){
            if(find(u)==find(v)||vis[v]==2) continue;//如果是已经缩点了或者是偶环直接继续,没有影响. 
            if(!vis[v]){	//如果没有被染色 
                vis[v]=2,pre[v]=u;
                if(!mh[v]){	//如果未被匹配,就进行匹配 
                    for(int x=v,last;x;x=last)	//增广路取反 
                        last=mh[pre[x]],mh[x]=pre[x],mh[pre[x]]=x;
                    return 1;
                }
                vis[mh[v]]=1,q.push(mh[v]); //否则将v的匹配结点染色,加入队列. 
            }
            else{
             int rt=LCA(u,v);	//找到LCA ,然后进行缩点. 
             blossom(u,v,rt),blossom(v,u,rt);
            }
        }
    }
    return 0;
    }
    int cal(){
        int ans=0;
        for(int i=1;i<=n;i++) if(!mh[i]&&aug(i)) ans++;	//最大匹配. 
        return ans;
    }
}T;
int main(){
	int TT;scanf("%d",&TT);while(TT--){
		int n,m,e;scanf("%d%d%d",&n,&m,&e);
		T.init(3*m+n);
		rep(i,1,e){
			int u,v;scanf("%d%d",&u,&v);
			int x=n+(v-1)*3;
			T.add(u,x+1);
			T.add(u,x+2);
			T.add(u,x+3);
		}
		rep(i,1,m){
			int x=n+(i-1)*3;
			T.add(x+1,x+2),T.add(x+1,x+3),T.add(x+2,x+3);
		}
		printf("%dn",T.cal()-n);
		rep(i,1,n){
			printf("%d ",(T.mh[i]-n-1)/3+1);
		}
		puts("");
	}
	return 0;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5580604.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存