1. 程式人生 > >題解-bzoj3901 棋盤遊戲

題解-bzoj3901 棋盤遊戲

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;
}