2015年ACM/ICPC合肥赛区 E题(二分图最大点独立集)

题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5267

2015年ACM/ICPC合肥赛区 E题(二分图最大点独立集)

2015年ACM/ICPC合肥赛区 E题(二分图最大点独立集)

题意:(真tm难读)给你一个n*m的土地,每个方格'.'表示空地,数字表示远古土地(一个连通块),然后你现在要建立牧场,可以建在空地上,也可以建在远古土地上(那么这个数字形成的连通块就是一个牧场),要求相邻的两个牧场不能相邻,求最多可以建多少牧场。

注意 输入的时候n m之间是有空格的。

思路:一看就是有关二分图最大匹配的。。。比赛的时候写了爆搜TLE,赛后补了2个小时莫名其妙WA。。。然后参考了网上的题解。思路完全正确,可能是细节出错误了吧。

因为一旦选定一个数字建牧场,那么包含这个数字的连通块都要建造牧场。所以枚举每个数字(不超过10)的建造情况,然后筛选合法状态,对合法状态下的空地,向其他空地和没建造牧场的远古土地建边(此处直接将二分图翻倍,求得最大匹配数/2)。

然后答案=空土地数量-最大匹配数/2+选定的数字个数。

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int maxn=150;
char s[maxn][maxn];
vector<pii>v[12];
int vis[12],dxy[]={0,1,0,-1,1,0,-1,0};
bool book[12][12];
int mp[12][12];
// 二分图最大匹配模板
struct Max_Match
{
    int n;
    vector<int>g[maxn];
    bool vis[maxn];
    int left[maxn];
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<=n;i++) g[i].clear();
        memset(left,-1,sizeof(left));
    }
    bool match(int u)
    {
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if(!vis[v])
            {
                vis[v]=true;
                if(left[v]==-1||match(left[v]))
                {
                    left[v]=u;
                    return true;
                }
            }
        }
        return false;
    }
    int solve()
    {
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            memset(vis,false,sizeof(vis));
            if(match(i)) ans++;
        }
        return ans;
    }
}MM;

int main()
{
    int T,cas=1,n,m;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
        for(int i=0;i<=10;i++) v[i].clear();
        memset(vis,0,sizeof(vis));
        //给含数字的方格编号,并记录其连通块的所有点
        int cnt=0;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(s[i][j]>='0'&&s[i][j]<='9')
            {
                if(!vis[(s[i][j]&15)])
                vis[(s[i][j]&15)]=++cnt;
                v[vis[(s[i][j]&15)]].push_back(pii(i,j));
            }
        }
        //枚举每个数字选或不选的状态 2的10次方
        int stu=(1<<cnt)-1,cou,ans=0;
        for(int i=0;i<=stu;i++)
        {
            cou=0;
            memset(book,0,sizeof(book));
            for(int j=0;j<cnt;j++)
            {
                //选的连通块及其周围的空地都标记为不可选
                if(i&(1<<j))
                {
                    cou++;
                    for(int k=0;k<v[j+1].size();k++)
                    {
                        int x=v[j+1][k].first;
                        int y=v[j+1][k].second;
                        book[x][y]=true;
                        for(int ii=0;ii<4;ii++)
                        {
                            int nx=x+dxy[ii];
                            int ny=y+dxy[ii+4];
                            if(nx<1||nx>n||ny<1||ny>m) continue;
                            if(s[nx][ny]=='.') book[nx][ny]=true;
                        }
                    }
                }
                else
                {
                    //没选的数字就标记不可选
                    for(int k=0;k<v[j+1].size();k++)
                    {
                        int x=v[j+1][k].first;
                        int y=v[j+1][k].second;
                        book[x][y]=true;
                    }
                }
            }
            //判断是否有两个选择的数字块相邻
            bool flag=false;
            for(int j=0;j<cnt;j++)
            {
                if(i&(1<<j))
                {
                    for(int k=0;k<v[j+1].size();k++)
                    {
                        int x=v[j+1][k].first;
                        int y=v[j+1][k].second;
                        for(int ii=0;ii<4;ii++)
                        {
                            int nx=x+dxy[ii];
                            int ny=y+dxy[ii+4];
                            if(nx<1||nx>n||ny<1||ny>m) continue;
                            if(s[nx][ny]=='.') continue;
                            int t=vis[(s[nx][ny]&15)];
                            if(i&(1<<(t-1)))
                            {
                                if(vis[(s[nx][ny]&15)]==vis[(s[x][y]&15)]) continue;
                                flag=true;
                            }
                        }
                    }
                }
            }
            if(flag) continue;
            //给剩下的可选空地重新标号
            int cnt=0;
            memset(mp,0,sizeof(mp));
            for(int r=1;r<=n;r++)
            for(int c=1;c<=m;c++)
            {
                if(s[r][c]=='.'&&!book[r][c])
                mp[r][c]=++cnt;
            }
            MM.init(cnt);
            //建边,将原图翻倍,求得最大匹配数/2
            for(int r=1;r<=n;r++)
            for(int c=1;c<=m;c++)
            if(s[r][c]=='.'&&!book[r][c])
            {
                for(int k=0;k<4;k++)
                {
                    int nx=r+dxy[k];
                    int ny=c+dxy[k+4];
                    if(nx<1||nx>n||ny<1||ny>m) continue;
                    if(s[nx][ny]=='.'&&!book[nx][ny])
                    {
                        MM.g[mp[r][c]].push_back(mp[nx][ny]);
                    }
                }
            }
            for(int r=1;r<=n;r++)
            for(int c=1;c<=m;c++)
            if(s[r][c]=='.'&&!book[r][c])
            {
                for(int k=0;k<4;k++)
                {
                    int nx=r+dxy[k];
                    int ny=c+dxy[k+4];
                    if(nx<1||nx>n||ny<1||ny>m) continue;
                    //空地和没选的数字块也可以建边
                    if(s[nx][ny]!='.')
                    {
                        int tmp=vis[(s[nx][ny]&15)]-1;
                        if(i&(1<<tmp)) continue;
                        MM.g[mp[r][c]].push_back(mp[nx][ny]);
                    }
                }
            }
            int tmp=cnt-MM.solve()/2;
            //答案=可选总点数-最大匹配/2+已选数字块个数
            ans=max(ans,tmp+cou);
        }
        printf("Case #%d: %d\n",cas++,ans);
    }
    return 0;
}