题解-bzoj3901 棋盘游戏

2019年第一篇文章 (。・∀・)ノ゙

Problem

bzoj无良权限题,拿学长的号交的

题目概要:给定一个\(n\times n\)的矩阵。令\(x=\frac {n+1}2\)。可以进行任意次以下操作:选择一个\(x\times x\)的子矩阵,将其中所有数乘上\(-1\)。求操作后矩阵元素和的最大值。\(n\leq 33\)且为奇数

Solution

这道题挺有意思的,两道题的思想一拼就成另外一道题了,而且毫不让人厌烦

发现数据范围十分有意思\(n\leq 33\),这个复杂度不是搜索就是折半搜索(废话)

其实更有意思的是\(x=\frac {n+1}2\),由于\(n\)为奇数,则\(2x=n+1\),画个图可以发现在这个棋盘里任意选两个\(x\times x\)的子矩形一定会有相交

画个图发现把子矩形移到四个角后会夹出来一个十字(就是第\(x\)行与第\(x\)列),考虑这个十字有特殊的意义

然后经过缜da密dan思cai考xiang发现这个十字是有对称的意义在里头的,也就是说\(a[1][i],a[1][x],a[1][i+x]\)的选择状态是有关联的,关联就是他们的选择状态的异或和为\(0\)(如果把选择设为\(1\),不选设为\(0\))。证明很简单,就是在矩形中画任意一个子矩形都只能将这三点中的两个或零个点包到矩形里头去,所以最终异或起来只能为\(0\)(即只需要知道这三个元素中的两个就能得到第三个)

类似的,发现除了左右关联(以第\(x\)列为轴)外,上下也是有关联的(以第\(x\)行为轴),到此发现了这是一个以中心十字为轴的矩形!(设第\(x\)行为行轴,第\(x\)列为列轴)

接下来就简单多了,由于\(n\leq 33\)的范围使得不能枚举所有点,那可以枚举行轴(而行轴又是以列轴为轴的,所以只需要枚举行轴的前\(x\)个元素即可推出整个行轴),然后枚举列轴的情况(同样只需要枚举前\(x\)个元素),发现如果行轴情况确定的情况下,列轴的\(x\)个元素带来的影响(即每一行的贡献)都是相互独立的(即不需要\(2^x\)去枚举),每个格子带来的影响也是独立的

上面可能讲得有点抽象,下面细化下过程 (还不懂就看代码吧)

  • 枚举第\(x\)行前\(x\)个格子选与不选 \(O(2^x)\)
  • 得出第\(x\)行整行的情况 \(O(n)\)
  • 枚举第\(i\)行第\(x\)个格子选与不选 \(O(n)\)
  • 由当前枚举状态枚举第\(i\)行每个格子\((i,j)\) \(O(n)\)
  • 推出格子\((i,j),(i+x,j),(i,j+x),(i+x,j+x)\) \(O(1)\)

总复杂度为\(O(2^xx^2)\),把\(x=17\)代进去大概为\(4e7\)

Code

#include <cstdio>

const int N=34;
int a[N][N],rw[N],ln[N];
int n,t;

inline int calc(int i,int j){
    int res=a[i][j],e;
    e=a[i+t][j],res+=(rw[j]?-e:e);
    e=a[i][j+t],res+=(ln[i]?-e:e);
    e=a[i+t][j+t],res+=(ln[i]^rw[j+t]?-e:e);
    return res>0?res:-res;
}

int main(){
    scanf("%d",&n);t=n+1>>1;
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
        scanf("%d",&a[i][j]);
    
    int ans=0;
    for(int lim=1<<t,S=0;S<lim;++S){
        int sum=0;
        for(int i=1;i<=t;++i)
            if(S&(1<<i-1))rw[i]=1,sum-=a[t][i];
            else rw[i]=0,sum+=a[t][i];
        for(int i=t+1;i<=n;++i){
            rw[i]=rw[t]^rw[i-t];
            if(rw[i])sum-=a[t][i];
            else sum+=a[t][i];
        }
        
        for(int i=1,r0,r1;i<t;++i){
            r0=a[i][t]+(rw[t]?-a[i+t][t]:a[i+t][t]);
            ln[i]=0;for(int j=1;j<t;++j)r0+=calc(i,j);
            
            r1=-a[i][t]+(rw[t]?a[i+t][t]:-a[i+t][t]);
            ln[i]=1;for(int j=1;j<t;++j)r1+=calc(i,j);
            sum+=r0>r1?r0:r1;
        }ans=ans>sum?ans:sum;
    }printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/penth/p/10205878.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值