###題目連接###html
題目大意:有 n 個正整數,每一個正整數表明一個成語,正整數同樣則成語相同。同一個正整數最多隻會出現 3 次。ios
求一種排列,使得這個排列中,相同成語的間隔最小值最大,輸出這個最小間隔的最大值。spa
相同成語的間隔爲這二者中間的成語個數。code
特別地,當每種成語都只出現一次時,把最小間隔的最大值視爲 n 。htm
分析:blog
一、若成語最大出現的次數是 2 時:好比有兩個不一樣的成語 1 2 ,他們都出現過兩次,那麼爲了使得最小間隔要最大,最好的構造就是兩個 1 的間隔等於兩個 2 的間隔。
get
1 2 X X X X X 1 2 ( X 表明別的成語)it
這樣就可使得出現過兩次的成語,他們的間隔都相同了,不會有哪個更小。那麼設 res = 只出現一次的成語個數,設 a = 出現爲兩次的成語種類數。io
則 res = n - 2 * a ,答案就是:res + (a - 1)。class
二、那麼對於成語沒有出現次數爲 2 ,有出現次數爲 3 時,也能夠這樣構造。 1 2 3 X X X 1 2 3 X X 1 2 3
這樣你會發現,1 2 3 這三個成語的最小間隔都同樣,那因爲這裏 X 爲奇數(X=5),分佔兩邊則必有一邊更少,那麼少的這一邊就是最小間隔了。
設 res = 出現一次的成語個數,a = 出現三次的成語種類數。則 res = n - 3 * a,答案就是:res + (a - 1)。
三、那麼對於既有兩次的又有三次出現的成語,也能夠用上面的思想構造:
因爲爲了使最小間隔最大,而對於出現三次的成語,最好是一個在最左一個在最右,一個在最中間。而對於出現兩次的成語,因爲沒有 「中間第三個成語」的限制,那麼爲了加大出現三次的成語的間隔數,最好的方法就是先最左最右排完出現三次的成語,而後才排出現兩次的成語。
好比這有出現三次的:1 2 3,出現兩次的 4 5 。
1 2 3 4 5 XX 1 2 3 X X X 4 5 1 2 3
這樣能夠最大限度的保證最小間隔儘可能小且 1 與 1 ,2 與 2 ,3與 3 的最小間隔相同(對於成語出現兩次的也是同樣的,它們間隔都相同)
但這裏有個極限想法:若是出現兩次的成語個數不少,致使它們一直往中間靠攏,可能會使某個出現兩次的成語的間隔會小於出現三次的成語的間隔,則同時求出來而後去 min 便可。
設 res = 出現一次的成語個數,x = 出現兩次的成語種類數,y = 出現三次的成語種類數。
則有:res = n - 2 * x - 3 * y,答案爲:min(res + y + x - 1, res / 2 + x + y - 1)
hhh 而後這個 min 其實沒用,能夠從公式中看出,右邊(成語出現爲三次的最小間隔)必定會小於左邊(成語出現爲兩次的最小間隔)。
代碼以下:
#include<iostream> #include<algorithm> using namespace std; int n; int a[100008],b[100008],c[100008], vis[100008]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++){ scanf("%d", &a[i]); b[i] = a[i]; } sort(b + 1, b + n + 1); int len = unique(b + 1, b + n + 1) - b - 1; for (int i = 1; i <= n; i++) c[i] = lower_bound(b + 1, b + len + 1, a[i]) - b, vis[c[i]] ++; int x = 0, y = 0; for (int i = 1; i <= len; i++){ if (vis[i] == 2) x++; else if (vis[i] == 3) y++; } int res, ans; if ((!x) && (!y)){ ans = n; } else if (x&&(!y)){ res = n - 2 * x; ans = res + x - 1; } else if ((!x)&&y){ res = (n - 3 * y) / 2; ans = res + y - 1; } else{ res = n - 2 * x - 3 * y; //ans = min(res + y + x - 1, res / 2 + x + y - 1); ans=res / 2 + x + y - 1; } printf("%d\n", ans); }