P3355 骑士共存问题题解

题目链接:P3355 骑士共存问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题解:

棋盘问题考虑黑白染色成为二分图后做。

观察马的性质,可知一个点只能到一个异色点,所以,构造方案可以先将所有同色点放上马,再考虑有那些异色点不可以放置。

方法一:

网络流,时间复杂度为O(|E|min(|E|0.5,|V|0.3))

从源点向每个白点连一条限制为1的边(黑色,白色都可以我选定先在白色放满马)(这里的1没有太大的意义,可以理解为每个点一匹马)

从白点向与它不可共存的点,连边,限制为1因为流量最大为1。

从黑点向汇点连一条限制为1的边。

最后答案为n*n-m-ans,表示总点数减去障碍点,再减去冲突的黑点。

#include <bits/stdc++.h>
using namespace std;
const int N=50010; 
const int M=500010;
int tot=1,n,m,s,t,nxt[M],go[M],hd[N],dep[N],cur[N],vis[N],jz[M],ans;
queue<int> q;
bool bfs()
{
       memset(dep,0,sizeof(dep));
       memcpy(cur,hd,sizeof(hd));
       q.push(s);
       dep[s]=1;
       while(!q.empty())
       {
              int u=q.front();
              q.pop();
              for(int i=hd[u];i;i=nxt[i])
               {
                        int v=go[i];
                        if(!jz[i]||dep[v])continue;
                        dep[v]=dep[u]+1;
                        q.push(v);
               }
       }
       return dep[t];
}
int dfs(int u,int flow)
{
       if(u==t)return flow;
       int out=0;
       for(int i=cur[u];i&&flow;i=nxt[i])
       {
                   cur[u]=i;
               int v=go[i];
               if(jz[i]&&dep[v]==dep[u]+1)
               {
                        int res=dfs(v,min(jz[i],flow));
                        if(res)
                        {
                                 jz[i]-=res;jz[i^1]+=res;flow-=res;out+=res;
                        }
               }
       }
       if(out==0) dep[u]=0;
       return out;
}
void add(int u,int v,int w)
{
       nxt[++tot]=hd[u];
       hd[u]=tot;
       go[tot]=v;
       jz[tot]=w;
}
int id(int x,int y)
{
    return (x-1)*n+y;
}
int xj[10]={2,2,-2,-2,1,1,-1,-1};
int yj[10]={1,-1,1,-1,2,-2,2,-2};
int main()
{
       scanf("%d%d",&n,&m);
       s=0,t=n*n+1; 
       for(int i=1;i<=m;i++)
       {
            int x,y;
            scanf("%d%d",&x,&y);
            vis[id(x,y)]=1;
       }
       for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                int ids=id(i,j);
                if(vis[ids])continue;
                if((i+j)%2==0)
                {
                    add(s,ids,1);
                    add(ids,s,0);
                    for(int k=0;k<8;k++)
                    {
                        int x=i+xj[k];
                        int y=j+yj[k];
                        if(x>0&&y>0&&x<=n&&y<=n&&vis[id(x,y)]==0)
                        {
                            add(ids,id(x,y),1e9);
                            add(id(x,y),ids,0);
                        } 
                    }
                }
                else
                {
                    add(ids,t,1);
                    add(t,ids,0);
                }
            }
        while(bfs())
        ans+=dfs(s,1e9);
        printf("%lld\n",n*n-m-ans);
        return 0;
}

方法二:
匈牙利算法。

从白点向限制的黑点连边,跑匈牙利,求最大匹配。

但是加了一个数据,匈牙利跑不过去,所以二分图的问题,最好转成网络流来做,更快。

#include <iostream>
#include <cstdio>
#include <cstring> 
using namespace std;
const int N=50010; 
const int M=500010;
int tot,n,m,nxt[M],go[M],hd[N],girl[N],ans,wz[210][210];
bool bk[N],vis[N];
int xj[10]={2,2,-2,-2,1,1,-1,-1};
int yj[10]={1,-1,1,-1,2,-2,2,-2};
inline int read(){
    int ans=0;char c;bool flag=true;
    for(;c<'0'||c>'9';c=getchar())if(c=='-')flag=false;
    for(;c>='0'&&c<='9';c=getchar())ans=ans*10+c-'0';
    return flag ? ans : -ans;
}
inline void add(int x,int y)
{
    nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
    return ;
}
inline bool find(int x)
{
    for(int i=hd[x];i;i=nxt[i])
    {
        int y=go[i];
        if(vis[y])continue;
        vis[y]=1;
        if(!girl[y]||find(girl[y]))
        {
            girl[y]=x;
            return 1;
        }
    }
    return 0;
}
inline int id(int x,int y)
{
    return (x-1)*n+y;
 } 
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        x=read(),y=read();
        bk[id(x,y)]=1;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            wz[i][j]=id(i,j);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(bk[wz[i][j]])continue;
            if((i+j)%2)
                for(int k=0;k<8;k++)
                {
                    int x=i+xj[k];
                    int y=j+yj[k];
                    if(x>0&&y>0&&x<=n&&y<=n&&bk[wz[x][y]]==0)
                        add(wz[i][j],wz[x][y]);                
                }
            
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if((i+j)%2&&!bk[wz[i][j]])
            {
                memset(vis,0,sizeof(vis));
                if(find(wz[i][j]))ans++;
            }        
    printf("%d\n",n*n-m-ans);
    return 0;
}

 

 

    

posted @ 2024-01-25 11:51  storms11  阅读(15)  评论(0编辑  收藏  举报