二分图匹配题集

ZOJ 3988  Prime Set

 这个题还是很怪,在建图是,“0元素”和“1元素”不明显,而在产生了最大匹配后,才感觉像是二分图。

首先素数筛得到素数后,判断符合条件的PrIme Set集合。

然后强行跑二分图最大匹配,得到最大匹配数$cnt$,同时得到集合中未匹配元素$num$。

如果$cnt≥k$,那我只要从最大匹配中取$k$对就可以了,答案$×2$,因为最大匹配两端本来就两两互不相交。

否则$cnt<k$, 我最后答案就是$cnt\times 2 + min(num, k - cnt)$,未匹配的元素之所以未匹配,就是因为之前和它相连的点,已经被进入到最大匹配中了,导致它失配,所以剩下的元素只能一个一个取了。

#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
const int maxn = 3e3 + 5;

vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    vis[x] = 1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                match[x] = y;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}

//线性素数筛
const int N = 2e6 + 50;
int prime[N], num_prime = 0;
int isNotPrime[N];
void is_prime(int N)
{
    isNotPrime[0] = isNotPrime[1] = 1;
    for(int i=2;i<N;i++)
    {
        if(!isNotPrime[i])
        {
            prime[num_prime++] = i;
        }
        for(int j=0;j<num_prime&&i*prime[j]<N;j++)
        {
            isNotPrime[i*prime[j]] = 1;
            if(!(i%prime[j]))
            {
                break;
            }
        }
    }
    return;
}

int a[maxn];

int main()
{
    is_prime(2e6 + 5);
    int T; scanf("%d", &T);
    while(T--)
    {
        int n, k; scanf("%d %d", &n, &k);
        for(int i = 1; i <= n; i++)
        {
            match[i] = -1; scanf("%d", &a[i]);
            g[i].clear();
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = i + 1; j <= n; j++)
            {
                if(!isNotPrime[a[i] + a[j]])
                {
                    add(i, j);
                    match[i] = match[j] = 0;
                }
            }
        }
        int cnt = 0;
        for(int i = 1; i <= n; i++)
        {
            if(!match[i])
            {
                memset(vis, 0, sizeof(vis));
                if(dfs(i))
                {
                    cnt++;
                }
            }
        }
        int num = 0;
        for(int i = 1; i <= n; i++)
        {
            if(!match[i])
            {
                num++;
            }
        }
        if(cnt >= k) printf("%d\n", k * 2);
        else printf("%d\n", cnt * 2 + min(num, k - cnt));
    }
    return 0;
}
Code

 

ZOJ 1564 Place the Robots

此题和POJ 2226 Muddy Fields很像。

#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
const int maxn = 2480 + 5;

vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    vis[x] = 1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                match[x] = y;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}

char s[55][55];
int l[55][55], c[55][55];
int main()
{
    int T, kase = 0; scanf("%d", &T);
    while(T--)
    {
        int n, m; scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++)
        {
            scanf("%s", s[i] + 1);
        }
        int num = 0;
        memset(l, 0, sizeof(l));
        memset(c, 0, sizeof(c));
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                if(s[i][j] == 'o' || s[i][j] == '*')
                {
                    if(!l[i][j - 1])
                    {
                        l[i][j] = ++num;
                    }
                    else l[i][j] = l[i][j - 1];
                    if(!c[i - 1][j])
                    {
                        c[i][j] = ++num;
                    }
                    else c[i][j] = c[i - 1][j];
                }
            }
        }
        for(int i = 1; i <= num; i++)
        {
            match[i] = 0;
            g[i].clear();
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                if(s[i][j] == 'o')
                {
                    add(l[i][j], c[i][j]);
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= num; i++)
        {
            if(!match[i])
            {
                memset(vis, 0, sizeof(vis));
                if(dfs(i))
                {
                    ans++;
                }
            }
        }
        printf("Case :%d\n%d\n", ++kase, ans);
    }
    return 0;
}
Code

 

POJ 1112 Team Them Up!

要求每个团队内的所有人互相认识,不考虑这一点还好说。

建补图的题也做过几个了,还不是特别敏感,这个题也是建补图POJ 2942。本题转换为补图后,那有边相连的两个点肯定在不同的团队中。

然后对每一个连通分量,跑二分图判定,把一个联通分量中的点分成两部分,设为$cnt[i][0],cnt[i][1]$,分别表示第$i$个连通分量中对等的两部分。

将$n$个物品分成两堆,使差值最小,是经典的$DP$问题。

$dp[i][j]=(dp[i-1][j-cnt[i][0] | dp[i-1][j-cnt[i][1]])$. $dp[i][j]$表示前$i$个连通分量中,每个分量取其中一个,能够构成的状态。顺着最后一个状态往前推打印路径。

不能用滚动数组,中间状态用来转移。

#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
int mp[111][111];
int g[111][111];
int cnt[111][3];
int flag = 0;
int vis[111];
vector<int> belong[111][3];
int n;
void dfs(int idx, int u, int color)
{
    vis[u] = color;
    cnt[idx][color]++;
    belong[idx][color].push_back(u);
    for(int i = 1; i <= n; i++)
    {
        if(i == u) continue;
        int v = i;
        if(g[u][v])
        {
            if(vis[v] == 0)
            {
                dfs(idx, v, 3 - color);
            }
            else if(vis[v] == color)
            {
                flag = 1;
                return;
            }
        }
    }
}
int dp[111][111];
void Print(int idx, int sum)
{
    if(idx <= 0) return;
    if(sum >= cnt[idx][1] && dp[idx - 1][sum - cnt[idx][1]])
    {
        for(int i = 0; i < (int)belong[idx][1].size(); i++)
        {
            int v = belong[idx][1][i];
            vis[v] = 1;
        }
        Print(idx - 1, sum - cnt[idx][1]);
    }
    else if(sum >= cnt[idx][2] && dp[idx - 1][sum - cnt[idx][2]])
    {
        for(int i = 0; i < (int)belong[idx][2].size(); i++)
        {
            int v = belong[idx][2][i];
            vis[v] = 1;
        }
        Print(idx - 1, sum - cnt[idx][2]);
    }
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int tmp;
        scanf("%d", &tmp);
        while(tmp)
        {
            g[i][tmp] = 1;
            scanf("%d", &tmp);
        }
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(g[i][j] == 0 || g[j][i] == 0)
            {
                g[i][j] = g[j][i] = 0;
            }
        }
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = i + 1; j <= n; j++)
        {
            if(g[i][j] == 1)
            {
                g[i][j] = g[j][i] = 0;
            }
            else
            {
                g[i][j] = g[j][i] = 1;
            }
        }
    }
    int num = 0;
    for(int i = 1; i <= n; i++)
    {
        if(!vis[i])
        {
            num++;
            dfs(num, i, 1);
        }
    }
    if(flag)
    {
        printf("No solution\n");
        return 0;
    }
    dp[0][0] = 1;
    for(int i = 1; i <= num; i++)
    {
        for(int j = 0; j <= n / 2; j++)
        {
            if(j >= cnt[i][1] && dp[i - 1][j - cnt[i][1]]) dp[i][j] = 1;
            if(j >= cnt[i][2] && dp[i - 1][j - cnt[i][2]]) dp[i][j] = 1;
        }
    }
    memset(vis, 0, sizeof(vis));
    int pre = 0;
    for(int i = n / 2; i >= 0; i--)
    {
        if(dp[num][i])
        {
            Print(num, i);
            pre = i;
            break;
        }
    }
    printf("%d ", pre);
    for(int i = 1; i <= n; i++)
    {
        if(vis[i])
        {
            printf("%d ", i);
        }
    }
    printf("\n");
    printf("%d ", n - pre);
    for(int i = 1; i <= n; i++)
    {
        if(!vis[i])
        {
            printf("%d ", i);
        }
    }
    return 0;
}

/*
2
1 2 0
2 1 0
*/
Code

 

POJ 3189 Steady Cow Assignment

 二分图的多重匹配问题。

d哥也说过,网络流解决多重匹配最高效,图很好建,但没注意到题目中B的范围,枚举最大排名和最小排名跑最大流就可以了。

#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
#include <queue>
using namespace std;

#define next Next
const int inf = 0x3f3f3f3f;
const int maxn=1200;
int level[maxn];
int iter[maxn];
int head[maxn],tot;
struct edge{
    int to,cap,Next;
} e[45000]; //此处应为边的两倍,加一条容量为0的反向边
void init(int n){
    for(int i = 1; i <= n; i++) head[i] = -1;
    tot=0;
}
void add(int from,int to,int cap){
    e[tot].Next=head[from];
    e[tot].to=to;
    e[tot].cap=cap;
    head[from]=tot;
    tot++;
}
void addedge(int from,int to,int cap){
    add(from,to,cap);
    add(to,from,0);
}
void bfs(int s){
    memset(level,-1,sizeof(level));
    queue<int> q;
    level[s]=0;
    q.push(s);
    while(!q.empty()){
        int v=q.front(); q.pop();
        for(int i=head[v];~i;i=e[i].Next){
            edge &ed=e[i];
            if(ed.cap>0&&level[ed.to]<0){
                level[ed.to]=level[v]+1;
                q.push(ed.to);
            }
        }
    }
}
int dfs(int v,int t,int f){
    if(v==t) return f;
    for(int &i=iter[v];~i;i=e[i].Next){
        edge &ed=e[i];
        if(ed.cap>0&&level[v]<level[ed.to]){
            int d=dfs(ed.to,t,min(f,ed.cap));
            if(d>0){
                ed.cap-=d;
                e[i^1].cap+=d;
                return d;
            }
        }
    }
    return 0;
}
int max_flow(int s,int t){
    int flow=0;
    while(1){
        bfs(s);
        if(level[t]<0) return flow;
        memcpy(iter,head,sizeof(iter));
        int f;
        while((f=dfs(s,t,inf))>0){
            flow+=f;
        }
    }
}
vector<int> g[1111];
int c[22];
int main()
{
    int n, b; scanf("%d %d", &n, &b);
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= b; j++)
        {
            int x; scanf("%d", &x);
            g[i].push_back(x);
        }
    }
    for(int i = 1; i <= b; i++) scanf("%d", &c[i]);
    int diff = 100;
    for(int i = 1; i <= b; i++)
    {
        for(int j = i; j <= b; j++)
        {
            init(n + b + 2);
            int s = n + b + 1;
            int t = n + b + 2;
            for(int k = 1; k <= n; k++)
            {
                addedge(s, k, 1);
            }
            for(int k = 1; k <= b; k++)
            {
                addedge(n + k, t, c[k]);
            }
            for(int k = 1; k <= n; k++)
            {
                for(int p = i - 1; p <= (j - 1); p++)
                {
                    addedge(k, n + g[k][p], 1);
                }
            }
            int flow = max_flow(s, t);
           // printf("%d\n", flow);
            if(flow == n)
            {
                diff = min(diff, j - i + 1);
                break;
            }
        }
    }
    printf("%d\n", diff);
    return 0;
}
Code

 

741C - Arpa’s overnight party and Mehrdad’s silent entering

二分图判定问题。

关键看怎么连边,男女朋友之间连边,$i\times 2 - 1$向$i\times2$连边,构成了长度为偶数的环。(不会证明)

 

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
vector<int> g[maxn];
int vis[maxn];
void dfs(int u, int col)
{
    vis[u] = col;
    for(int i = 0; i < (int)g[u].size(); i++)
    {
        int v = g[u][i];
        if(!vis[v])
        {
            dfs(v, 3 - col);
        }
    }
}
int u[maxn], v[maxn];
int main()
{
    int n; scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d %d", &u[i], &v[i]);
        g[u[i]].push_back(v[i]);
        g[v[i]].push_back(u[i]);
    }
    for(int i = 1; i <= 2 * n; i += 2)
    {
        g[i].push_back(i + 1);
        g[i + 1].push_back(i);
    }
    for(int i = 1; i <= 2 * n; i++)
    {
        if(!vis[i])
        {
            dfs(i, 1);
        }
    }
    for(int i = 1; i <= n; i++)
    {
        printf("%d %d\n", vis[u[i]], vis[v[i]]);
    }
    return 0;
}
Code

 

posted @ 2018-10-15 21:06  汪汪鱼  阅读(321)  评论(0编辑  收藏  举报