【SDOI省队集训题】棋盘(最小费用流)

这里写图片描述
这里写图片描述
题解:
是一个最小费用最大流,建立源点汇点,x向可以互相攻击到的为一个集体,所有的集体作为一纵,连源点;同样,y向可以互相攻击到的为一个集体,所有的集体为一纵,与汇点连边;不难得出,x[i]y[j]对应唯一一个点,这样的x与y连边流量1,费用0。每次找流,就在这个集团x与S再连一条1,++h[v];T与集团y也连一条
下面图示
这里写图片描述
代码:
这个代码是非计划连边,按理说一个集团有多少个点才连多少边,但是非计划连边由于流量限制应该也是对的,还是写了一个计划连边的。

#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>
#define INF 1e9+7
#define N 25000+5
using namespace std;
bool vis[N];
int tot=-1,x[55][55],y[55][55],d[2505],s,t,hv[2505],ans[N];
char a[55][55];
int nxt[N*2+5],point[N*2+5],remind[N*2+5],v[N*2+5],last[N*2+5],dis[N*2+5],co[N*2+5];
void addline(int x,int y,int cap,int cost)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=cap; co[tot]=cost;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; co[tot]=-cost;
}
bool spfa()
{
    queue<int>q;
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        vis[now]=false;
        for (int i=point[now];i!=-1;i=nxt[i])
          if (dis[v[i]]>dis[now]+co[i] && remind[i])
          {
            dis[v[i]]=dis[now]+co[i];
            last[v[i]]=i;
            if (!vis[v[i]])
            {
                vis[v[i]]=true; q.push(v[i]);
            }
          }
    }
    return dis[t]<INF;
} 
int main()
{
    freopen("chess.in","r",stdin);
    freopen("chess.out","w",stdout);
    int n,i,j,q,cnt=0;
    scanf("%d",&n);
    memset(point,-1,sizeof(point));
    memset(nxt,-1,sizeof(nxt));
    for(int i=1;i<=n;i++)
      scanf("%s",a[i]+1);
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        {
            ++cnt;
            for (;j<=n && a[i][j]=='.';j++) x[i][j]=cnt;
        }
    int tt=cnt;
    for (j=1;j<=n;j++)
      for (i=1;i<=n;i++) 
        {
            ++cnt;
            for (;i<=n && a[i][j]=='.';i++) y[i][j]=cnt;
        }
    int maxx=0,l;
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        if (a[i][j]=='.') 
          addline(x[i][j],y[i][j],1,0);
    for (i=1;i<=tt;i++)addline(0,i,1,0);
    for (i=tt+1;i<=cnt;i++)addline(i,cnt+1,1,0); 
    ans[0]=0; int num=0;  
    s=0,t=cnt+1;int k,la;
    while (spfa())//如果还有流 
    {
        ++num;
        ans[num]=ans[num-1]+dis[t];//最大流量肯定是1 
        int now=t;
        while (now)
        {   
            remind[last[now]]--;
            remind[last[now]^1]++;  
            la=now;
            now=v[last[now]^1];
        }
        addline(0,la,1,++hv[la]);
        addline(v[last[t]^1],t,1,++hv[v[last[t]^1]]);
    }
    scanf("%d",&q);
    for (i=1;i<=q;i++) 
    {
        scanf("%d",&k);
        printf("%d\n",ans[k]);
    } 
}

以下是计划连边,速度慢于非计划。

#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>
#define INF 1e9+7
#define N 25000+5
using namespace std;
bool vis[N];
int tot=-1,x[55][55],y[55][55],d[2505],s,t,hv[2505],ans[N];
char a[55][55];
int nxt[N*2+5],point[N*2+5],remind[N*2+5],v[N*2+5],last[N*2+5],dis[N*2+5],co[N*2+5];
void addline(int x,int y,int cap,int cost)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=cap; co[tot]=cost;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; co[tot]=-cost;
}
bool spfa()
{
    queue<int>q;
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        vis[now]=false;
        for (int i=point[now];i!=-1;i=nxt[i])
          if (dis[v[i]]>dis[now]+co[i] && remind[i])
          {
            dis[v[i]]=dis[now]+co[i];
            last[v[i]]=i;
            if (!vis[v[i]])
            {
                vis[v[i]]=true; q.push(v[i]);
            }
          }
    }
    return dis[t]<INF;
} 
int main()
{
    freopen("chess.in","r",stdin);
    freopen("chess.out","w",stdout);
    int n,i,j,q,cnt=0;
    scanf("%d",&n);
    memset(point,-1,sizeof(point));
    memset(nxt,-1,sizeof(nxt));
    for(int i=1;i<=n;i++)
      scanf("%s",a[i]+1);
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        {
            ++cnt;
            for (;j<=n && a[i][j]=='.';j++) 
              x[i][j]=cnt,addline(0,cnt,1,hv[cnt]++);
        }
    int tt=cnt;
    for (j=1;j<=n;j++)
      for (i=1;i<=n;i++) 
        {
            ++cnt;
            for (;i<=n && a[i][j]=='.';i++) y[i][j]=cnt;
        }
    int maxx=0,l;s=0,t=cnt+1;
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        if (a[i][j]=='.') 
          addline(x[i][j],y[i][j],1,0);

    for (j=1;j<=n;j++)
      for (i=1;i<=n;i++) 
        {
            ++tt;
            for (;i<=n && a[i][j]=='.';i++) addline(tt,t,1,hv[tt]++);
        }
    ans[0]=0; int num=0;  
    int k,la;
    while (spfa())//如果还有流 
    {
        ++num;
        ans[num]=ans[num-1]+dis[t];//最大流量肯定是1 
        int now=t;
        while (now)
        {   
            remind[last[now]]--;
            remind[last[now]^1]++;  
            now=v[last[now]^1];
        }
    }
    scanf("%d",&q);
    for (i=1;i<=q;i++) 
    {
        scanf("%d",&k);
        printf("%d\n",ans[k]);
    } 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值