[BZOJ2668][CQOI2012]交换棋子(费用流)

题目:

我是超链接

题解:

能够看出来这道题目是网络流,但并不清楚这个图怎么建
这种有交换次数限制的要考虑分成两半:最多流入的数量,最多流出的数量 作为流量限制
我们对于每个节点拆成三个点:x1,x2,x3,一个点的流量限制为val[i][j]
整体的连接方向是x1->x2->x3

如果只有原图中这个点是黑点
<x1,x2>.cap=val[i][j]/2cost=0<x2,x3>.cap=(val[i][j]+1)/2cost=0 < x 1 , x 2 > . c a p = v a l [ i ] [ j ] / 2 , c o s t = 0 ; < x 2 , x 3 > . c a p = ( v a l [ i ] [ j ] + 1 ) / 2 , c o s t = 0 ; <script type="math/tex" id="MathJax-Element-3">< x1,x2 >.cap=val[i][j]/2,cost=0;< x2,x3 >.cap=(val[i][j]+1)/2,cost=0;</script>

然后 S>x1cap=1cost=0 S − > x 1 , c a p = 1 , c o s t = 0

如果只有新图中这个点是黑点
<x1,x2>.cap=(val[i][j]+1)/2cost=0<x2,x3>.cap=val[i][j]/2cost=0 < x 1 , x 2 > . c a p = ( v a l [ i ] [ j ] + 1 ) / 2 , c o s t = 0 ; < x 2 , x 3 > . c a p = v a l [ i ] [ j ] / 2 , c o s t = 0 ; <script type="math/tex" id="MathJax-Element-5">< x1,x2 >.cap=(val[i][j]+1)/2,cost=0;< x2,x3 >.cap=val[i][j]/2,cost=0;</script>

然后 x2>Tcap=1cost=0 x 2 − > T , c a p = 1 , c o s t = 0

如果原图和新图这个点的颜色一样(都黑/白)
<x1,x2>.cap=val[i][j]/2cost=0<x2,x3>.cap=val[i][j]/2cost=0 < x 1 , x 2 > . c a p = v a l [ i ] [ j ] / 2 , c o s t = 0 ; < x 2 , x 3 > . c a p = v a l [ i ] [ j ] / 2 , c o s t = 0 ; <script type="math/tex" id="MathJax-Element-7">< x1,x2 >.cap=val[i][j]/2,cost=0;< x2,x3 >.cap=val[i][j]/2,cost=0;</script>

然后对于那些互相可以交换的点连接< x3,y1 >.cap=INF,cost=1,流过这条边表示交换一次。
然后我们愉快的跑最小费用最大流就好了,zkw优化一用就很顺手了。

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#define INF 1e9
using namespace std;
const int N=2000;
const int cc[8][2]={{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}};
int tot,point[N],dis[N],v[N*10],remind[N*10],c[N*10],nxt[N*10],yt[25][25],xt[25][25],val[25][25],ans;bool vis[N];
char st[25];
void addline(int x,int y,int z,int vv)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=z; c[tot]=vv;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; c[tot]=-vv;
}
bool spfa(int s,int t)
{
    queue<int>q;q.push(s);
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    while (!q.empty())
    {
        int now=q.front(); q.pop(); vis[now]=0;
        for (int i=point[now];i!=-1;i=nxt[i])
          if (dis[v[i]]>dis[now]+c[i] && remind[i]) 
          {
            dis[v[i]]=dis[now]+c[i];
            if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
          }
    }
    return dis[t]<INF;
}
int dfs(int now,int t,int limit)
{
    vis[now]=1;
    if (now==t || !limit) return limit;
    int flow=0,f;
    for (int i=point[now];i!=-1;i=nxt[i])
      if (remind[i] && !vis[v[i]] && dis[v[i]]==dis[now]+c[i])
      {
        f=dfs(v[i],t,min(limit,remind[i]));
        flow+=f;
        limit-=f;
        remind[i]-=f;
        remind[i^1]+=f;
        if (!limit) return flow;
      }
    return flow;
}
int zkw(int s,int t)
{
    while (spfa(s,t))
    {
        memset(vis,0,sizeof(vis));
        ans+=dis[t]*dfs(s,t,INF);
    }
}
int main()
{
    tot=-1;memset(point,-1,sizeof(point));
    int n,m;scanf("%d%d",&n,&m);

    int s=0,t=n*m*3+1;
    for (int i=1;i<=n;i++)
    {
        scanf("%s",st+1);
        for (int j=1;j<=m;j++) yt[i][j]=st[j]-'0';
    }
    for (int i=1;i<=n;i++)
    {
        scanf("%s",st+1);
        for (int j=1;j<=m;j++) xt[i][j]=st[j]-'0';
    }
    for (int i=1;i<=n;i++)
    {
        scanf("%s",st+1);
        for (int j=1;j<=m;j++) val[i][j]=st[j]-'0';
    }int a=0,b=0;
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++)
      {
        int id=(i-1)*m+j;
        if (yt[i][j]==xt[i][j]) addline(id,id+n*m,val[i][j]/2,0),addline(id+n*m,id+2*n*m,val[i][j]/2,0);
        else if (xt[i][j]==1) a++,addline(id,id+n*m,(val[i][j]+1)/2,0),addline(id+n*m,id+2*n*m,val[i][j]/2,0),addline(id+n*m,t,1,0);
        else if (yt[i][j]==1) b++,addline(id,id+n*m,val[i][j]/2,0),addline(id+n*m,id+2*n*m,(val[i][j]+1)/2,0),addline(s,id+n*m,1,0);
      }
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++)
      {
        int id=(i-1)*m+j+2*n*m;
        for (int k=0;k<8;k++)
        {
            int xx=i+cc[k][0],yy=j+cc[k][1],jd=(xx-1)*m+yy;
            if (xx<=0 || yy<=0 || xx>n || yy>m) continue;
            addline(id,jd,INF,1);
        }
      }
    zkw(s,t);
    if (a==b) printf("%d",ans);else printf("-1");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值