首页 > 代码库 > BZOJ 1854 游戏(二分图匹配或并查集)

BZOJ 1854 游戏(二分图匹配或并查集)

此题的二分图匹配做法很容易想,就是把属性当做s集,武器当做t集,如果该武器拥有该武器则连一条边。

那么答案就是求该二分图的最大前i个匹配。将匈牙利算法改一改,当前找不到增广路就break。

但是过这个题需要常数优化,不能每次都fillchar一遍used数组。可以用队列将使用的used点加入,然后需要初始化的时候弹出即可。

技术分享
# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
# define lowbit(x) ((x)&(-x))
# define pi 3.1415926535
# define eps 1e-9
# define MOD 1000000009
# define INF 1000000000
# define mem(a,b) memset(a,b,sizeof(a))
# define FOR(i,a,n) for(int i=a; i<=n; ++i)
# define FO(i,a,n) for(int i=a; i<n; ++i)
# define bug puts("H");
# define lch p<<1,l,mid
# define rch p<<1|1,mid+1,r
# define mp make_pair
# define pb push_back
typedef pair<int,int> PII;
typedef vector<int> VI;
# pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
int Scan() {
    int res=0, flag=0;
    char ch;
    if((ch=getchar())==-) flag=1;
    else if(ch>=0&&ch<=9) res=ch-0;
    while((ch=getchar())>=0&&ch<=9)  res=res*10+(ch-0);
    return flag?-res:res;
}
void Out(int a) {
    if(a<0) {putchar(-); a=-a;}
    if(a>=10) Out(a/10);
    putchar(a%10+0);
}
const int N=1000005;
//Code begin...
 
struct Edge{int to, next;}edge[N<<1];
int head[N], tot, linker[N], uN;
bool used[N];
queue<int>Q;
 
void init(){tot=0; mem(head,-1);}
void addedge(int u, int v){
    edge[tot].to=v; edge[tot].next=head[u]; head[u]=tot++;
}
bool dfs(int u){
    for (int i=head[u]; i!=-1; i=edge[i].next) {
        int v=edge[i].to;
        if (!used[v]) {
            used[v]=true;
            Q.push(v);
            if (linker[v]==-1||dfs(linker[v])){linker[v]=u; return true;}
        }
    }
    return false;
}
int hungary(){
    int res=0;
    mem(linker,-1);
    FOR(u,1,uN) {
        while (!Q.empty()) {
            int v=Q.front(); Q.pop();
            used[v]=0;
        }
        if (dfs(u)) res++;
        else break;
    }
    return res;
}
int main ()
{
    int n, u, v;
    n=Scan();
    init();
    FOR(i,1,n) {
        u=Scan(); v=Scan();
        uN=max(uN,u); uN=max(uN,v);
        addedge(u,i); addedge(v,i);
    }
    printf("%d\n",hungary());
    return 0;
}
View Code

 

这题的并查集做法还是神啊。。。

把一个有a,b两种属性的武器看成点a,b之间的无向边

对于一个联通块,假如不含环(就是一棵树),那么必定可以满足其中任意的p-1个点。

对于一个联通块,假如含环,那么必定全部的p个点都能满足。

那么合并并查集的时候可以利用一个vis来维护这个性质,把权值看成点,把武器看成边。如果每次加入的边是合并两个联通块

就把权值小的联通块并到权值大的联通块,然后给权值小的vis=true.如果不是,就把该联通块的顶点的vis=true

这样就可以保证,如果一个大小为N联通块1.=N-1条边构成,最大点的vis=false,其他为true

2.≥N条边构成,所有点的vis=true。

那么对于当前最小的i使得vis[i]=false.那么i-1一定是可以满足的最大数量。

简单证明:从小到大来看,对于没有关联边的点,显然vis[i]=false. 关联边的点,我们每次都可以选择一个联通块上的点,且这些点占用的边可以保持不同。

技术分享
# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
# define lowbit(x) ((x)&(-x))
# define pi 3.1415926535
# define eps 1e-9
# define MOD 1000000009
# define INF 1000000000
# define mem(a,b) memset(a,b,sizeof(a))
# define FOR(i,a,n) for(int i=a; i<=n; ++i)
# define FO(i,a,n) for(int i=a; i<n; ++i)
# define bug puts("H");
# define lch p<<1,l,mid
# define rch p<<1|1,mid+1,r
# define mp make_pair
# define pb push_back
typedef pair<int,int> PII;
typedef vector<int> VI;
# pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
int Scan() {
    int res=0, flag=0;
    char ch;
    if((ch=getchar())==-) flag=1;
    else if(ch>=0&&ch<=9) res=ch-0;
    while((ch=getchar())>=0&&ch<=9)  res=res*10+(ch-0);
    return flag?-res:res;
}
void Out(int a) {
    if(a<0) {putchar(-); a=-a;}
    if(a>=10) Out(a/10);
    putchar(a%10+0);
}
const int N=1000005;
//Code begin...
 
int fa[N];
bool vis[N];
 
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void un(int x, int y){
    if (x<y) swap(x,y);
    vis[y]=1; fa[y]=x;
}
int main ()
{
    int n, u, v, p, q;
    n=Scan();
    FOR(i,1,n+1) fa[i]=i;
    FOR(i,1,n) {
        u=Scan(); v=Scan();
        p=find(u), q=find(v);
        if (p==q) vis[p]=1;
        else un(p,q);
    }
    FOR(i,1,n+1) if (!vis[i]) {printf("%d\n",i-1); break;}
    return 0;
}
View Code

 

BZOJ 1854 游戏(二分图匹配或并查集)