【洛谷2423】[HEOI2012] 朋友圈(最大团)
- 有\(A,B\)两个国家。\(A\)国每人有一个友善值\(a_i\),\(B\)国每人有一个友善值\(b_i\)。
- 对于两个\(A\)国人(友善值为\(x,y\)),当且仅当\((x\oplus y)\mod2=1\)时二者互为朋友。
- 对于两个\(B\)国人(友善值为\(x,y\)),当且仅当\((x\oplus y)\mod2=0\)或\(x\ or\ y\)有奇数个\(1\)时二者互为朋友。
- 然后有\(m\)组关系表示\(A\)国某一个人和\(B\)国某一个人互为朋友。
- 求最多能从两国中选出多少人,使他们两两互为朋友。
- \(|A|\le200,|B|\le200\)或者\(|A|\le10,|B|\le3000\)
对于\(A\)国人
发现两个\(A\)国人互为朋友的条件是友善值奇偶性不同。
根据抽屉原理,三个\(A\)国人不可能两两互为朋友,因此我们最多选出两个\(A\)国人来。
因此直接暴枚\(A\)国人的选择情况,只保留所有与这些\(A\)国人有连边的\(B\)国人求解答案。
对于\(B\)国人
我们发现,两个奇偶性相同的\(B\)国人之间必有边,两个奇偶性不同的\(B\)国人可能有边。
也就是说,奇偶性相同的\(B\)国人之间已经形成了团。
考虑一个基本结论:最大团=总点数-补图最大匹配。
而这张图的补图,就是一张二分图!
那么只要做一次二分图最大匹配就可以了。
额,算算复杂度好像匈牙利算法卡满会变成\(O(B^3)\)?反正跑得飞快。
代码:\(O(A^2B^3)\)
#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 3000
using namespace std;
int A,B,m,ans,a[N+5],b[N+5],p[N+5],w[N+5];vector<int> v[N+5];vector<int>::iterator it;
namespace HungarianAlgorithm//匈牙利算法
{
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
int ee,lnk[N+5],s[N+5],vis[N+5];struct edge {int to,nxt;}e[N*N+5];
I bool C(RI x) {RI t=0;W(x) x&=x-1,t^=1;return t;}//求二进制下1的个数的奇偶性
I bool Mark(CI x,CI ti)//二分图匹配
{
for(RI i=lnk[x];i;i=e[i].nxt)
{
if(!p[e[i].to]||vis[e[i].to]==ti) continue;vis[e[i].to]=ti;
if(!s[e[i].to]||Mark(s[e[i].to],ti)) return s[e[i].to]=x,true;
}return false;
}
I void Solve(RI t)//根据当前保留的点(p[x]=1的点)求解
{
RI i,j;for(ee=0,i=1;i<=B;++i) lnk[i]=s[i]=vis[i]=0,t+=p[i];//初始化,t统计总点数
for(i=1;i<=B;++i) if(p[i]&&b[i]&1) for(j=1;j<=B;++j) p[j]&&!(b[j]&1)&&!C(b[i]|b[j])&&add(i,j);//暴力建补图
for(i=1;i<=B;++i) p[i]&&b[i]&1&&Mark(i,i)&&--t;ans=max(ans,t);//二分图匹配,最大团=总点数-补图最大匹配
}
}
int main()
{
using namespace HungarianAlgorithm;
#define Cl(a) for(RI i=1;i<=B;++i) a[i]=0;//清空数组
#define For_v(x) for(it=v[x].begin();it!=v[x].end();++it)//遍历vector
RI Tt,i,j,x,y;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%d%d",&A,&B,&m),ans=0,i=1;i<=A;++i) scanf("%d",a+i),v[i].clear();//读入,同时清空
for(i=1;i<=B;++i) scanf("%d",b+i);for(i=1;i<=m;++i) scanf("%d%d",&x,&y),v[x].push_back(y);
for(i=1;i<=B;++i) p[i]=1;Solve(0);for(i=1;i<=A;++i) {Cl(p);For_v(i) p[*it]=1;Solve(1);}//0个A;1个A
for(i=1;i<=A;++i) for(j=i+1;j<=A;++j) if((a[i]^a[j])&1)//2个A
{Cl(w);For_v(i) w[*it]=1;Cl(p);For_v(j) p[*it]=w[*it];Solve(2);}
printf("%d\n",ans);
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒