很妙,一个筐最多装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-------- #includeusing 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{ vector e[N]; queue q; 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; }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)