【最大团转最大点独立集(匈牙利算法+时间戳优化)】BZOJ2744-[HEOI2012]朋友圈
【题目大意】
有两个国家A和B。存在以下朋友关系:
1.A国:每个人都有一个友善值,当两个A国人的友善值a、b,如果a xor b mod 2=1,那么这两个人都是朋友,否则不是;
2.B国:每个人都有一个友善值,当两个B国人的友善值a、b,如果a xor b mod 2=0
或者 (a or b)化成二进制有奇数个1,那么两个人是朋友,否则不是朋友;
3.A、B两国之间的人也有可能是朋友,数据中将会给出A、B之间“朋友”的情况。
4.在AB两国,朋友圈的定义:一个朋友圈集合S,满足S∈A∪B,对于所有的i,j∈S,i和j是朋友。
求最大的朋友圈的人数。
【思路】
这道题求的是最大团。首先将题目大意翻译一下:
1.A国之间,友善值为一奇一偶的为朋友;B国之间,友善值同奇偶性或有(a or b)二进制有奇数个1的为朋友(后面一个条件真没办法翻译,到时候特判一下就好了)。两个之间的直接输入。
2.显然A国至多取出两个人。也就是由三种情况:取0个人、取1个人、取2个人。
3.那么我们就枚举从A国取出来的人,然后再B中删去与那两个人不是朋友的成员①,则只需要单独考虑B中的情况啦!
4.最大团显然是没有办法做的,不妨对B集合取个反图,也就是原来没有边的现在连上,原来有的现在不连。简单来说,就是同奇同偶间不连边,奇偶之间如果满足(a or b)化成二进制有偶数个1就连边。显然是一个二分图!这样问题就转换为最大点独立集。最大点独立集=总数-二分图匹配。
【优化】
直接跑Hungary是过不去的。问题在哪里呢?因为一直memset太挫了!
这个时候我们需要用到时钟T1、T2和tim、vis两个数组。还有思路①处的ban数组也注意一下。
到底什么是时间戳呢?好像并没有人解释。弄了一天大致弄出了一个比较清晰的解释:
一般来说匈牙利算法是这样弄得。每次匈牙利算法前将lk数组清空为-1(整个过程中枚举了A中0个1个2个3个人),然后匈牙利算法内部的循环中将vis数组清零。还是这句话——太挫了!!!!
T1在每次枚举开始+1。它用在lk数组上。由于lk数组并没有清空,之前可能已经lk过了,但是事实上每次匈牙利算法,lk是要清空的。所以如果tim[x]的值!=T1,就表示当前这次枚举中这个点还没有连接过,相当于lk[x]=0;如果tim[x]=T1了,说明当前这次已经用过它了,也就是之前清零过了的含义,那么按照朴素的匈牙利来做。
T2则在匈牙利算法中的循环语句中使用,出现在find前,这个是减去vis数组优化的,道理和上面差不多。vis[x]=T2表示当前这次find已经访问过了,而vis[x]!=T2就表示当前没有访问过,相当于vis=0或1。
另外,ban也是一个道理,ban[x]=T1表示当前这次x被ban掉了。
综上所述,时间戳并不是像是一些地方所说的一样用来在上一次匈牙利算法的基础上进行增广,而是真的、纯粹地免去memset的过程而已……
再简单点概述,如果有i次操作,每次vis数组都要清空的话,那么第i次操作不如这样转换:!vis[x]→vis[x]!=i,vis[x]→vis[x]=i。
所以上面因为lk数组每次枚举只要清零一次,ban数组也只需要ban一次,所以T1只在三种情况的的开头清空;而vis数组在for循环中每次都要清空,所以同理T2也要一直+1。
哇,其实是一个好简单的东西…………并没有时间戳这个名字来得那么高大上。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 const int MAXA=200+50; 8 const int MAXB=3000+50; 9 int na,nb,m; 10 int a[MAXA],b[MAXB]; 11 bool map[MAXA][MAXB]; 12 vector<int> E[MAXB]; 13 int T1=0,T2=0,ban[MAXB],tim[MAXB],vis[MAXB],lk[MAXB]; 14 15 void addedge(int u,int v) 16 { 17 E[u].push_back(v); 18 } 19 20 int count(int x) 21 { 22 int re=0; 23 while (x) 24 { 25 re+=x&1; 26 x>>=1; 27 } 28 return re; 29 } 30 31 int find(int x) 32 { 33 if (ban[x]==T1) 34 return 0; 35 for (int i=0;i<E[x].size();i++) 36 { 37 int to=E[x][i]; 38 if ((ban[to]!=T1) && (vis[to]!=T2)) 39 { 40 vis[to]=T2; 41 if (tim[to]!=T1 || !lk[to] || find(lk[to])) 42 { 43 tim[to]=T1; 44 lk[to]=x; 45 return 1; 46 } 47 } 48 } 49 return 0; 50 } 51 52 int mis(int x=0,int y=0) 53 { 54 ++T1; 55 int ret=0; 56 for (int i=1;i<=nb;i++) 57 if (map[x][i] || map[y][i]) ban[i]=T1,++ret; 58 for (int i=1;i<=nb;i++) 59 if (!(b[i]&1)) 60 { 61 ++T2; 62 if (find(i)) ++ret; 63 } 64 return (nb-ret); 65 } 66 67 void init() 68 { 69 memset(map,1,sizeof(map)); 70 scanf("%d%d%d",&na,&nb,&m); 71 for (int i=1;i<=na;i++) scanf("%d",&a[i]); 72 for (int i=1;i<=nb;i++) scanf("%d",&b[i]); 73 for (int i=0;i<m;i++) 74 { 75 int x,y; 76 scanf("%d%d",&x,&y); 77 map[x][y]=0; 78 } 79 } 80 81 void solve() 82 { 83 for (int i=1;i<=nb;i++) 84 if (!(b[i]&1)) 85 for (int j=1;j<=nb;j++) 86 if (b[j]&1) 87 if (!(count(b[i]|b[j])&1)) 88 addedge(i,j); 89 for (int i=1;i<=nb;i++) map[0][i]=0; 90 int ans=mis(); 91 for (int i=1;i<=na;i++) 92 ans=max(ans,mis(i)+1); 93 for (int i=1;i<=na;i++) 94 if (a[i]&1) 95 for (int j=1;j<=na;j++) 96 if (!(a[j]&1)) 97 ans=max(ans,mis(i,j)+2); 98 printf("%d",ans); 99 } 100 101 int main() 102 { 103 init(); 104 solve(); 105 return 0; 106 }