【LuoguP4142】洞穴遇险

题目链接

题意

自己看题面

Sol

一个格子只能被占用一次,n只有50?
有什么想法?

DP? 退火? 贪心?

先来看看这个柱子是个啥玩意

首先只用把拐角出放在一个X+Y是奇数的格子
然后他会占掉另外两个格子,并且是它周围的相邻的格子

发现一个东西:占掉的一定是一个X和Y均是偶数的格子和一个X和Y均是奇数的格子

把这两个分开考虑? 感觉放了一个柱子就等价于走了一遍上面提到过的三种格子

这样一个网络流的模型就出来了,由于有权值是费用流

你不觉得是三分图最大权匹配?

我们做过三分图最大匹配,显然就把中间那一列每一个点拆为两个点,正好还能很好地体现不稳定度的概念,看出来每增广一次就是放了一个柱子

那么建图就比较显然了

第一列到第二列是如果点是相邻的则连容量为 1 费用为 0 的边,第三列到第四列同理,第二列到第三列的相同点则连容量为 1 费用为危险度的边,然后源点向第一列,第四列向汇点连容量
为 1 费用为 0 的边。

然后跑最大费用最大流,当此次增广的费用是负的了或者增广了m次时就 break 。如果你没有判断增广的费用是否为负的话 就会WA得很惨,因为可能根本就放不下 个石头,后面增广的费用是为了得到最大流而退流形成的,并不需要石头越多越好,我们只希望费用最大。

P.S. : 这道题建图其实并不是很难,但要想到点子上,把点分好类

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<queue>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read(){
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
bool vis[51][51];
int id[51][51];
int val[51][51];
typedef long long ll;
const int INF=2147483637;
int n,m,k;int cnt=0;
const int dx[5]={0,-1,0,1,0};
const int dy[5]={0,0,1,0,-1};
#define CK(a,b) (a>0&&b>0&&a<=n&&b<=n&&!vis[a][b])
namespace EK{
    const int N=51;
    const int MAXN=5000;
    typedef long long ll;
    typedef pair<int,int> Pr;
    struct edge{//网络流建图
        int to,next,w,cap;
    }a[50000];
    int head[MAXN];int cnt=0;int cn=0;
    int id[N][N];int id2[N][N];
    int S=0,T;int ans=0;
    inline void add(int x,int y,int cap,int w){
        a[cn]=(edge){y,head[x],w,cap};head[x]=cn++;
    }
    void Build_Graph(){
        T=++cnt;Set(head,-1);
        for(register int i=1;i<=n;++i)
            for(register int j=1;j<=n;++j){
                if(vis[i][j]) continue;
                register int x,y;
                if(val[i][j]) {
                    add(id[i][j],id2[i][j],1,val[i][j]);add(id2[i][j],id[i][j],0,-val[i][j]);
                    for(register int k=1;k<=4;++k){
                        x=i+dx[k],y=j+dy[k];
                        if((x&1)||(y&1)) continue;//中间点向偶偶点
                        if(!CK(x,y)) continue;
                        add(id2[i][j],id[x][y],1,0);add(id[x][y],id2[i][j],0,0);
                    }
                }
                else{
                    if((i&1)&&(j&1)){//奇奇
                        add(S,id[i][j],1,0);add(id[i][j],S,0,0);
                        for(register int k=1;k<=4;++k){
                            x=i+dx[k],y=j+dy[k];
                            if(!val[x][y]) continue;if(!CK(x,y)) continue;//奇奇点向奇点连边
                            add(id[i][j],id[x][y],1,0);add(id[x][y],id[i][j],0,0);
                        }
                    }
                    else if((!(i&1))&&(!(j&1))){//偶偶
                        add(id[i][j],T,1,0);add(T,id[i][j],0,0);//只要向汇点连边就行了
                    }
                }
            }
        return ;
    }
    int pre_ed[MAXN];int dis[MAXN];bool in[MAXN];
    queue<int> Q;
    bool spfa(){
        while(!Q.empty()) Q.pop();
        Q.push(S);in[S]=1;Set(dis,-127/3);dis[S]=0;register int NO=dis[1];
        Set(pre_ed,-1);
        while(!Q.empty()){
            register int u=Q.front();Q.pop();
            for(register int v,i=head[u];~i;i=a[i].next){
                v=a[i].to;
                if(a[i].cap<=0) continue;
                if(dis[v]<dis[u]+a[i].w){
                    dis[v]=dis[u]+a[i].w;pre_ed[v]=i^1;
                    if(!in[v]) Q.push(v),in[v]=1;
                }
            }
            in[u]=0;
        }
        if(dis[T]==NO||dis[T]<=0) return 0;
        ans-=dis[T];
        register int flow=2147483647;
        register int u=T;
        while(u!=S) flow=min(flow,a[pre_ed[u]^1].cap),u=a[pre_ed[u]].to;
        for(u=T;u!=S;u=a[pre_ed[u]].to){
            register int pre=pre_ed[u];
            a[pre^1].cap-=flow;a[pre].cap+=flow;
        }
        return 1;
    }
    void work(){
        while(spfa()){
            --m;if(!m) break;
        }
        printf("%d\n",ans);
    }
}
int main()
{
    n=read();m=read();k=read();
    for(register int i=1;i<=n;++i) for(register int j=1;j<=n;++j) {
            val[i][j]=read(),EK::id[i][j]=++EK::cnt;EK::ans+=val[i][j];
            if(val[i][j]!=0) EK::id2[i][j]=++EK::cnt;
        }
    register int x,y;
    for(register int i=1;i<=k;++i) {x=read();y=read();vis[x][y]=1;}
    EK::Build_Graph();
    EK::work();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值