解题报告:luogu P2423 [HEOI2012]朋友圈【最大团转最大点独立集(匈牙利算法+时间戳优化)】

在这里插入图片描述

图的最大团:”任意两点之间都有一条边相连“的子图被称为无向图的团,点数最多的团为图的最大团

朋友圈中任意两个点之间都有关系,既是图中的团。
答案就是图中的最大团。

我们如果把B国的人分成奇数和偶数两类,就会发现奇数和偶数这两部分都是一个团
而且这两部分之间有一些连边
很像二分图是吧,就只是左右两边的点从两两没边变成了两两有边
于是我们取一个补图,这张图就变成了一张二分图
补图有一个非常好的性质,补图最大独立集等于原图最大团
这个很好理解吗,最大团要求两两有边,最大独立集要求两两没边,于是把边的存在性取反之后两者是等价的而二分图的最大独立集等于总点数-最小点覆盖
最小点覆盖等于最大匹配
于是B国的情况我们就解决了
再来看看A国和跨国关系
A国就是一个 n 2 n^2 n2的二分图,因此最多只选两个人就可以把整个国家给覆盖掉了。
因此A国中只能选择0,1,2人这三种情况,于是我们枚举在A国里选择哪些人,之后处理出B国中的和这些人都有朋友关系的人,对这些点跑最大独立集就好了
【优化】
直接跑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。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXA=200+50;
const int MAXB=3000+50;
int na,nb,m;
int a[MAXA],b[MAXB];
bool map[MAXA][MAXB];
vector<int> E[MAXB];
int T1=0,T2=0,ban[MAXB],tim[MAXB],vis[MAXB],lk[MAXB];

void addedge(int u,int v)
{
   
    E[u].push_back(v);
}

int count(int x)
{
   
    int re=0;
    while (x)
    {
   
        re+=x&1;
        x>>=1;
    }
    return re;
}

int find(int x)
{
   
    if (ban[x]==T1)
        return 0;
    for (int i=0;i<E[x].size();i++)
    {
   
        int to=E[x][i];
        if ((ban[to]!=T1) && (vis[to]!=T2))
        {
   
            vis[to]=T2;
            if (tim[to]!=T1 || !lk[to] || 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值