BZOJ 2756 [SCOI2012]奇怪的游戏

网络流。

确实是网络流题目做太少,完全没往这方面去想。。。

对棋盘进行黑白相间染色,如果记s0表示黑格子数字和,s1表示白格子数字和,t1表示黑格子数量,t2表示白格子数量,操作之后数字全都是x,那么因为黑白格子的操作次数肯定一样,有
xt1s1=xt0s0
可以得到
x=s1s0t1t0

如果 t1t2 那么x是唯一的,直接判断即可
否则假设x可行,那么任何大于x的数一定可行,于是二分即可

至于怎么判断,网络流!
源点->所有黑格 容量是格子的数字
黑格->相邻白格 容量无限大
所有白格->汇点 容量是格子的数字
判断最大流是否等于x与所有数字差的和即可

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define N 42
#define INF (1LL<<50)
using namespace std;
struct edge{int next,to;long long v;}e[N*N*4*2];
long long s[2], a[N][N];
int S, T, last[N*N], p_cnt, t[2];
int n, m, cnt, pack[N][N], dx[4]={0,0,1,-1}, dy[4]={1,-1,0,0}, level[N*N];
bool color[N][N];
void add(int a, int b, long long w)
{
    e[++cnt]=(edge){last[a],b,w};
    last[a]=cnt;

    e[++cnt]=(edge){last[b],a,0};
    last[b]=cnt;
}
bool bfs()
{
    queue<int> q;
    memset(level,-1,sizeof(level));
    q.push(S);
    level[S]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i = last[x]; i; i=e[i].next)
        {
            int y=e[i].to;
            if(e[i].v && level[y]==-1)
            {
                level[y]=level[x]+1;
                q.push(y);
            }
        }
    }
    return level[T]!=-1; 
}
long long dfs(int x, long long f)
{
    long long re=0;
    if(x==T)return f;
    for(int i = last[x]; i; i=e[i].next)
    {
        int y=e[i].to;
        if(level[x]+1!=level[y] || !e[i].v)continue;
        long long use=dfs(y,min(f-re,e[i].v));
        e[i].v-=use;
        e[i^1].v+=use;
        re+=use;
        if(re==f)return f;
    }
    if(!re)level[x]=-1;
    return re;
}
long long dinic()
{
    long long re=0;
    while(bfs())
        re+=dfs(S,INF);
    return re;
}
bool check(long long x)
{
    memset(last,0,sizeof(last));
    cnt=1;
    S=0, T=n*m+2;
    long long pre=0;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            if(color[i][j])
            {
                pre+=x-a[i][j];
                add(S,pack[i][j],x-a[i][j]);
                for(int k = 0; k < 4; k++)
                {
                    int nx=i+dx[k], ny=j+dy[k];
                    if(nx<1||ny<1||nx>n||ny>m)continue;
                    add(pack[i][j],pack[nx][ny],INF);
                }
            }
            else 
                add(pack[i][j],T,x-a[i][j]);
        }
    return dinic()==pre;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        p_cnt=0;
        memset(s,0,sizeof(s));
        memset(t,0,sizeof(t));
        long long mx=0;
        scanf("%d%d",&n,&m);

        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            {
                scanf("%lld",&a[i][j]);
                mx=max(mx,a[i][j]);
                pack[i][j]=++p_cnt;
            }

        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            {
                color[i][j]=(i+j)&1;
                s[color[i][j]]+=a[i][j];
                t[color[i][j]]++;
            }

        if(t[0]!=t[1])
        {
            long long x=(s[0]-s[1])/(t[0]-t[1]);
            if(x<mx)printf("-1\n");
            else printf("%lld\n",check(x)?(x*t[0]-s[0]):-1);
        }       
        else
        {
            if(s[0]!=s[1])printf("-1\n");
            else
            {
                long long l = mx, r = INF;
                while(l<r)
                {
                    long long mid=(l+r)>>1;
                    if(check(mid))r=mid;
                    else l=mid+1;
                }
                printf("%lld\n",l*t[1]-s[1]);
            }
        }
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值