「JOI 2017 Final」JOIOI 王國
題目描述
JOIOI 王國是一個 HHH 行 WWW 列的長方形網格,每個 1×11\times 11×1 的子網格都是一個正方形的小區塊。為了提高管理效率,我們決定把整個國家劃分成兩個省 JOI 和 IOI 。
我們定義,兩個同省的區塊互相連線,意為從一個區塊出發,不用穿過任何一個不同省的區塊,就可以移動到另一個區塊。有公共邊的區塊間可以任意移動。 我們不希望劃分得過於複雜,因此劃分方案需滿足以下條件:
- 區塊不能被分割為兩半,一半屬 JOI 省,一半屬 IOI 省。
- 每個省必須包含至少一個區塊,每個區塊也必須屬於且只屬於其中一個省。
- 同省的任意兩個小區塊互相連線。
- 對於每一行/列,如果我們將這一行/列單獨取出,這一行/列裡同省的任意兩個區塊互相連線。這一行/列內的所有區塊可以全部屬於一個省。
現給出所有區塊的海拔,第 iii 行第 jjj 列的區塊的海拔為 Ai,jA_{i,j}Ai,j。設 JOI 省內各區塊海拔的極差(最大值減去最小值) 為 RJOIR_{JOI}RJOI,IOI 省內各區塊海拔的極差為 RIOIR_{IOI}RIOI。在劃分後,省內的交流有望更加活躍。但如果兩個區塊的海拔差太大,兩地間的交通會很不方便。 因此,理想的劃分方案是 max(RJOI,RIOI)\max(R_{JOI}, R_{IOI})max(RJOI,RIOI) 儘可能小。 你的任務是求出 max(RJOI,RIOI)\max(R_{JOI}, R_{IOI})max(RJOI,RIOI) 至少為多大。
輸入格式
第一行,兩個整數 H,WH,WH,W,用空格分隔。 在接下來的 HHH 行中,第 iii 行有 WWW 個整數 Ai,1,Ai,2,…,Ai,WA_{i,1}, A_{i, 2}, \ldots, A_{i, W}Ai,1,Ai,2,…,Ai,W,用空格分隔。 輸入的所有數的含義見題目描述。
輸出格式
一行,一個整數,表示 max(RJOI,RIOI)\max(R_{JOI}, R_{IOI})max(RJOI,RIOI) 可能的最小值。
樣例
樣例輸入 1
4 4
1 12 6 11
11 10 2 14
10 1 9 20
4 17 19 10
樣例輸出 1
11
樣例解釋 1
在這組樣例中,一種理想方案長這樣。下圖中,JJJ 表示該區塊屬於 JOI 省,III 表示該區塊屬於 IOI 省。
JJJ |
JJJ |
JJJ |
III |
JJJ |
JJJ |
JJJ |
III |
JJJ |
JJJ |
III |
III |
JJJ |
III |
III |
III |
注意下述方案不符合第四條原則,將第三列單獨取出時,兩個 III 不能互相連線
JJJ |
JJJ |
III |
III |
JJJ |
JJJ |
JJJ |
III |
JJJ |
JJJ |
III |
III |
JJJ |
III |
III |
III |
樣例輸入 2
8 6
23 23 10 11 16 21
15 26 19 28 19 20
25 26 28 16 15 11
11 8 19 11 15 24
14 19 15 14 24 11
10 8 11 7 6 14
23 5 19 23 17 17
18 11 21 14 20 16
樣例輸出 2
18
資料範圍與提示
對於 15%15\%15% 的資料,H,W⩽10H, W\leqslant 10H,W⩽10。 對於另外 45%45\%45% 的資料,H,W⩽200H, W\leqslant 200H,W⩽200。 對於所有資料,2⩽H,W⩽2000,Ai,j⩽109(1⩽i⩽H,1⩽j⩽W)2\leqslant H, W\leqslant 2000, A_{i,j}\leqslant 10^9(1\leqslant i\leqslant H, 1\leqslant j\leqslant W)2⩽H,W⩽2000,Ai,j⩽109(1⩽i⩽H,1⩽j⩽W)。
————————————————————————————————華麗的分割線—————————————————————————————————————
【及其不正經的題解】
不想看的往後翻(我知道你們就只看程式碼)
咳咳,這道題是是呂神考試的T2。 T1是道思維量挺高的貪心,T3是道期望dp,我都懶得做(我絕對不會告訴你是因為我太菜了),所以我開始看T2。
一共大概寫了3個小時,200行後,我成功A掉了樣例,正當我高興的手舞足蹈,以為要在呂神的考試上切題時,我隨手寫了個小資料,結果竟然hack掉了我的程式碼!(我怎麼那麼欠)
我的心態就崩了。懷著忐忑的心情,我把程式碼交了上去,結果得了6分(旁邊低一年級的小學弟交了個錯的dfs,拿了個8分)
關鍵是,我tmd寫的是正解!
我那獐頭鼠目百拙千醜鷹頭雀腦鵠面鳩形不堪入目鳶肩豺目烏面鵠形的程式碼就暫且不放在這裡了,有想看的可以私聊
下面簡單介紹一下我的思路
【題解】
首先,要看懂這題,必須先看懂這句話“對於每一行/列,如果我們將這一行/列單獨取出,這一行/列裡同省的任意兩個區塊互相連線。這一行/列內的所有區塊可以全部屬於一個省”。
這句話是什麼意思呢?
我們先來看三個圖(A代表joi,B代表ioi)
(1)
AAAAAAAAAB
AAAAAABBBB
AABBBBBBBB
AAAAABBBBB
AAAAAAAABB
(2)
AAAAAAAAAA
AAAAAAAAAA
AAAABAAAAA
AABBBBAAAA
BBBBBBBBAA
(3)
AAAAAAAABB
AAAAAAABBB
AAAAAABBBB
AABBBBBBBB
ABBBBBBBBB
如果你讀懂了上面的話,就知道上面三個圖中,只有圖3是合法的。
也就是說,你在切分兩個國家時,只能畫一條單調的線。
但是一個國家既可以左右延伸,也可以上下延伸。
這怎麼辦呢?
我們可以先寫從左開始,向右延伸的方法,然後分別把圖旋轉90,180,270度,這樣四種情況就都齊了。
但得到了圖該怎樣做?
我們注意一下這道題的輸出是什麼:max(RJOI,RIOI) 儘可能小。
最小值最大?沒錯,就是你二分,出來吧!
我們首先得到最大值與最小值,並且答案肯定小於最大值-最小值。
假定最大值在joi,最小值在ioi,我們從左上角逐行掃蕩,每行的joi王國方格數不超過上一行的(不考慮每行方格數不少於上一行的這種情況)
二分一個答案,只要當前二分中點mid大於等於這個方格與最大值就選它去構造ioi王國,然後再去判斷ioi王國中的元素是不是都滿足條件
只要不符合就跳轉到下一行。
想到了這裡,這題就基本結束了。是不是很神奇呢?
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> using namespace std; #define INF 0x3f3f3f3f int n,m,a[2010][2010],ans=INF,maxn=-INF,minn=INF; inline int read()//ССµÄ¶ÁÈëÓÅ»¯£¬¿ÉÒÔ²»Ð´ { int x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=ch-'0'+(x<<3)+(x<<1); return x*f; } int MAX(int a,int b) { if(a>b) return a; if(a<=b) return b; } int MIN(int a,int b)//ÊÖ´òºÃÏñ¿ìÒ»µã { if(a<b) return a; if(a>=b) return b; } void turn()//Ðýת90¶È { for(int i=1;i<=n;i++) for(int j=1;j<=m/2;j++) swap(a[i][j],a[i][m-j+1]); } void choose()//Ñ¡Ôñ { for(int i=1;i<=n/2;i++) for(int j=1;j<=m;j++) swap(a[i][j],a[n-i+1][j]); } bool check(int mid) { int f=m+1; for(int i=1;i<=n;i++) { int t=0; for(int j=1;j<=min(f,m);j++) if(maxn-a[i][j]<=mid) t=max(t,j);//ÕÒiÐеÄ×î´óÑÓÉì else break; f=t; for(int j=t+1;j<=m;j++) if(a[i][j]-minn>mid) return 0;//ÅжÏÂú×ãÌõ¼þ } return 1; } int find_ans()//¶þ·Ö { int l=0,r=maxn-minn+1; while(l<=r) { int mid=(l+r)>>1; if(check(mid)) r=mid-1; else l=mid+1; } return l; } void find_ans2()//ö¾Ù4ÖÖÇé¿ö { ans=min(ans,find_ans()); turn(); ans=min(ans,find_ans()); choose(); ans=min(ans,find_ans()); turn(); ans=min(ans,find_ans()); } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { a[i][j]=read(); maxn=max(a[i][j],maxn); minn=min(a[i][j],minn); } find_ans2(); printf("%d\n",ans); return 1;//防抄 }
本蒟蒻第一次發題解,望各位大神指出不足,謝謝。