[JZOJ5126]棋盘/[HackerRank-HourRank12]Jumping Rooks

题目大意

给定一个 n×n 的棋盘,棋盘上每个位置要么为空要么为障碍。定义棋盘上两个位置 (x,y) (u,v) 能互相攻击当且仅当满足以下两个条件:
 x=u y=v
 (x,y) (u,v) 之间的所有位置,均不是障碍。

q 个询问,每个询问给定 ki ,要求从棋盘中选出 ki 个空位置来放棋子,问最少互相能攻击到的棋子对数是多少?
n50,q104,k 棋盘中空位置数量

题目分析

考虑将所有的行连通块和列连通块提出来,一个空位会同时属于一个行连通块和一个列连通块。
两个空位显然最多只会同属一个连通块。一个连通块如果有 x 个点,那么其会造成 (x1)x2 的代价。
考虑使用网络流,将所有的行连通块和列连通块都建点,分居两侧,原点向每一个行连通块连 n 条边,流量上限都是 1 ,第 i 条边的费用是 i1 ,列连通块向汇点连边类似。对于一个空位,我们在其两个连通块之间连一条流量上限为正无穷,费用为 0 的边。然后直接跑最小费用流最大流就好了。为了处理询问,我们直接每次只流 1 的流量然后计入答案就行了。如果你想跑得更快可以动态加边。
时间复杂度 O(Maxflow(n2,n3))

代码实现

#include <algorithm>
#include <iostream>
#include <climits>
#include <cstdio>
#include <cctype>
#include <queue>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

char getnxt()
{
    char ch=getchar();
    while (ch!='.'&&ch!='#') ch=getchar();
    return ch;
}

int buf[30];

void write(int x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int N=55;
const int K=N*N;
const int V=N*N*3;
const int E=(N*N*2+N*N*N*2)<<1;
const int INF=INT_MAX/2;

int rid[N][N],cid[N][N];
bool pic[N][N];
int ans[K];
int n,q,cnt1,cnt2,pts;

struct network
{
    int nxt[E],tov[E],r[E],f[E],v[E],c[E];
    int last[V],dis[V],fr[V],ed[V];
    bool exist[V];
    queue<int> q;
    int S,T,mincost,tot;

    void insert(int x,int y,int full,int cost,int rev){tov[++tot]=y,nxt[tot]=last[x],f[tot]=full,c[tot]=cost,r[tot]=tot+rev,last[x]=tot;}

    void addedge(int x,int y,int full,int cost){insert(x,y,full,cost,1),insert(y,x,0,-cost,-1);}

    void build()
    {
        S=0,T=pts+cnt1+cnt2+1;
        for (int i=1;i<=n;++i)
            for (int j=1;j<=n;++j)
                addedge(rid[i][j]+pts,cid[i][j]+pts+cnt1,1,0);
        for (int i=1;i<=cnt1;++i)
            for (int j=0;j<n;++j)
                addedge(S,i+pts,1,j);
        for (int i=1;i<=cnt2;++i)
            for (int j=0;j<n;++j)
                addedge(i+pts+cnt1,T,1,j);
    }

    void aug()
    {
        for (int x=S;x<=T;++x) dis[x]=INF,fr[x]=-1;
        dis[S]=0,q.push(S),exist[S]=1;
        for (int x,i,y;!q.empty();)
            for (i=last[x=q.front()],q.pop(),exist[x]=0;i;i=nxt[i])
                if (f[i]-v[i]&&dis[y=tov[i]]>dis[x]+c[i])
                {
                    dis[y]=dis[x]+c[i],fr[y]=x,ed[y]=i;
                    if (!exist[y]) q.push(y),exist[y]=1;
                }
        if (dis[T]==INF) return;
        for (int x=T,i;x!=S;x=fr[x]) i=ed[x],++v[i],--v[r[i]];
        mincost+=dis[T];
    }
}net;

void pre()
{
    cnt1=0;
    for (int i=1;i<=n;++i)
        for (int j=1;j<=n;++j)
            if (pic[i][j]&&!rid[i][j])
            {
                ++cnt1;
                for (int x=i,y=j;y<=n&&pic[x][y];++y) rid[x][y]=cnt1;
            }
    for (int j=1;j<=n;++j)
        for (int i=1;i<=n;++i)
            if (pic[i][j]&&!cid[i][j])
            {
                ++cnt2;
                for (int x=i,y=j;x<=n&&pic[x][y];++x) cid[x][y]=cnt2;
            }
    net.build();
    for (int i=1;i<=pts;++i) net.aug(),ans[i]=net.mincost;
}

int main()
{
    freopen("chess.in","r",stdin),freopen("chess.out","w",stdout);
    n=read(),pts=0;
    for (int i=1;i<=n;++i)
        for (int j=1;j<=n;++j)
            pts+=pic[i][j]=getnxt()=='.';
    pre();
    for (q=read();q--;write(ans[read()]),putchar('\n'));
    fclose(stdin),fclose(stdout);
    return 0;
}

你可能感兴趣的:(费用流,OI,hackerrank)