當七夕遇上算法競賽
2018/8/17 農歷 七月初七
今天這個特殊的日子,不知道各位算友都過得怎麽樣~
適逢七夕,寫算法題當然是眾汪所歸啊!所以今天,我們帶來了一道以七夕為背景的題目。
題目:七夕祭(BZOJ3032有權限,可在JoyOI和CodeVS上查找提交)
題目背景:
七夕節因牛郎織女的傳說而被扣上了「情人節」的帽子。於是TYVJ今年舉辦了一次線下七夕祭。Vani同學今年成功邀請到了cl同學陪他來共度七夕,於是他們決定去TYVJ七夕祭遊玩。
題目描述:
TYVJ七夕祭和11區的夏祭的形式很像。矩形的祭典會場由N排M列共計N×M個攤點組成。雖然攤點種類繁多,不過cl只對其中的一部分攤點感興趣,比如章魚燒、蘋果糖、棉花糖、射的屋……什麽的。Vani預先聯系了七夕祭的負責人zhq,希望能夠通過恰當地布置會場,使得各行中cl感興趣的攤點數一樣多,並且各列中cl感興趣的攤點數也一樣多。
不過zhq告訴Vani,攤點已經隨意布置完畢了,如果想滿足cl的要求,唯一的調整方式就是交換兩個相鄰的攤點。兩個攤點相鄰,當且僅當他們處在同一行或者同一列的相鄰位置上。由於zhq率領的TYVJ開發小組成功地扭曲了空間,每一行或每一列的第一個位置和最後一個位置也算作相鄰。現在Vani想知道他的兩個要求最多能滿足多少個。在此前提下,至少需要交換多少次攤點。
輸入格式:
第一行包含三個整數N和M和T。T表示cl對多少個攤點感興趣。
接下來T行,每行兩個整數x, y,表示cl對處在第x行第y列的攤點感興趣。
輸出格式:
首先輸出一個字符串。如果能滿足Vani的全部兩個要求,輸出both;如果通過調整只能使得各行中cl感興趣的攤點數一樣多,輸出row;如果只能使各列中cl感興趣的攤點數一樣多,輸出column;如果均不能滿足,輸出impossible。
如果輸出的字符串不是impossible, 接下來輸出最小交換次數,與字符串之間用一個空格隔開。
提示:
對於30% 的數據,N, M≤100。
對於70% 的數據,N, M≤1000。
對於100% 的數據,1≤N, M≤100000,0≤T≤min(NM, 100000),1≤x≤N,1≤y≤M。
輸入樣例:
樣例輸入1
2 3 4
1 3
2 1
2 2
2 3
樣例輸入2
3 3 3
1 3
2 2
2 3
輸出樣例:
樣例輸出1
row 1
樣例輸出2
both 2
提示:環形均分紙牌、中位數的性質。
思路:
首先我們只要想一想就會發現只要用 t 去模 n和m 就能根據是否整除得到字符串輸出是哪個。而且整除的結果就是最後移動後的均攤結果。
然後就可以發現這是個橫向和縱向的均分紙牌有木有?(均分紙牌是noip2002的一道經典貪心題目,並不難,希望掌握以後再往下看……不懂記得問)
不加證明地給出行和列分別求就可以了不會影響,因為這一行的點互相交換並不會影響這一列的點的個數。
那我們以行為例:
在均分紙牌中,我們會用a[i] = c[i](實際上這行的數量)-average(均分後的結果)來便於操作,所以a[i]可能是正的也可能是負的,a[i]==0就說明這一行已經達成目標。
而 abs(a[i]) 就是要對這一行進行的操作數,通過前綴和s[i] = a[i] + s[i-1],我們可以通過 ∑mi=1 | s[i] | (m是行數)得到最後所需的總移動次數。
以上為均分紙牌的內容,並沒有詳細講。接下來只剩下一個問題了,就是這並不是原來的均分紙牌,此題由於可以畫環,所以並不能完全把前綴和 s[i] 加起來就解決了。
此處希望讀者在腦中、紙上自行想象如果第一行和最後一行相接會出現怎樣的、比常規更巧的分配方式。
然後當我們不知道怎麽處理之時神來之筆就是:一定存在一種最優解的方案,環上某兩個相鄰的人之間沒有發生紙牌交換操作。因此我們可以在這裏把環斷開,就當成從第k+1個人開始依次往下分,像原來一樣均分紙牌,一直分到第k個人結束。
普通的均分紙牌是這樣的:
我們從第k+1個開始的均分紙牌是這樣的:
所以由於s[m]肯定是等於0的,我們所需結果從變成了
接下來的問題是如果遍歷k的話復雜度撐不住。
巧求k,化公式為問題:坐標軸上有m個點,怎樣取一個第k點,使得各點到此點的距離之和最短。
結論:1~m個點的坐標排序後的中位數第k個點即是所需點。
簡略說明:假如k點左邊現在有P個點,右邊現在有Q個點,當k點在坐標軸上向右移動一個單位時,左邊每個點都遠離它一個單位,右邊每個點都接近它一個單位,總距離比剛才減少了Q-P個單位;這個過程繼續下去,當k為中位點是P=Q,沒法再少了,再往右走就過分了,同理得往左走了。
所以回到問題,可以把 s[i] 排序後直接取中位點即可。
至此所有的問題都解決了,如果都理解了的話可以自己動手解決了,如還有沒理解可以結合代碼看看、搜搜資料、後臺留言……最大復雜度(nlogn+mlogm)
1 #include <bits/stdc++.h> 2 #define maxn 100005 3 using namespace std; 4 5 int n, m, t, flag; 6 long long ans; 7 int rows[maxn], columns[maxn]; 8 9 void solve(int *a, int c) 10 { 11 int average = t / c; 12 int s[maxn] = {0}; 13 14 for (int i = 1; i <= c; i++) 15 a[i] -= average, s[i] = a[i] + s[i-1]; 16 sort (s+1, s+1+c); 17 for (int i = 1; i <= c; i++) 18 ans += abs(s[i] - s[(c+1)>>1]); 19 } 20 21 int main() 22 { 23 scanf("%d%d%d", &n, &m, &t); 24 for (int i = 0; i < t; ++i) 25 { 26 int x, y; 27 scanf("%d%d", &x, &y); 28 rows[x]++; 29 columns[y]++; 30 } 31 32 if (t % n == 0) 33 { 34 flag++; 35 solve(rows, n); 36 } 37 if (t % m == 0) 38 { 39 flag += 2; 40 solve(columns, m); 41 } 42 43 if (flag == 0) printf("impossible"); 44 else if (flag == 1) printf("row "); 45 else if (flag == 2) printf("column "); 46 else if (flag == 3) printf("both "); 47 48 if (flag) printf("%lld", ans); 49 }
當七夕遇上算法競賽