1. 程式人生 > >HDU 6052 2017 Multi-University Training Contest

HDU 6052 2017 Multi-University Training Contest

題意:給出一個n*m(1<=n,m<=100)的矩陣,每個矩陣元素有一個顏色值ai(0<=ai<=10000),現在定義一個子矩陣的value為子矩陣中不同顏色的數量,求子矩陣value的期望。

題解:期望=所有子矩陣的value總和/子矩陣個數。首先解決一個zz的問題:子矩陣有多少個,二重迴圈列舉子矩形右下角的點(i,j),那麼子矩陣左上角的點一定在(0,0)-(i,j)這個矩陣裡,所以有i*j種。

如果你學過小學數學,當然,子矩陣總共有n*(n+1)*m*(m+1)/4=[n*(n+1)/2]*[m*(m+1)/2]=C(n+1,2)*C(m+1,2)種。(感謝 小壞蛋_千千 的糾正)

***************在此感謝UFO__的評論***************

接下來我們講重點:顯然要每個顏色單獨考慮,在考慮顏色i的時候,把顏色i的點看作關鍵點,求出 至少包含一個關鍵點的子矩陣個數。現在的問題是我們如何不重複不遺漏的統計個數。emmm先排個序(行號升序,列號升序)。把每個合法的矩陣算在序最小的那個關鍵點頭上,這樣就可以保證不重複,不遺漏。那麼我們再找包含第一個關鍵點的矩陣的時候,顯然沒有任何限制,只需要包含這個點就行了。找第二個關鍵點的矩陣的時候,不能包含第一個點……找第i個關鍵點決定的矩陣的時候,不能包含1..i-1這i-1個點。

那麼假設我們的圖長這個樣子,且灰色點是已經計數完成的點,白色點是未計數的點,黑色點是正在計數的點。

這個黑點位於(4,4)位置,我們現在要做的就是確定上下左右四個邊界分別有多少種選取方式,顯然白色點的序大於黑色點,黑色點的矩陣可以包含他們也可以不包含他們,因此下邊界無限制,可以取到(4,5,6==n)三種方式,而由於黑點同一行的左邊(同行右邊的也是白色點)以及上邊的行有不能包含的點,因此一個矩陣上邊界對應了左右的最遠邊界。我們需要列舉矩陣的上邊界(4,3,2,1),並且因為上邊界i-1的時候上邊界i要考慮的點仍然要考慮,而且要額外考慮i-1這行的灰色點。因此左右最遠邊界要持續進行維護。上邊界是ii的時候,考慮所有ii行的灰色點,在黑點左邊的去更新左邊界最遠點,在黑店右邊的去更新有邊界最遠點,而剛好在黑點上邊的話,說明這一行不可能作為上邊界了。這個時候就結束計算黑點名下的子矩形。開始計算下一個白點。

優化:我們看第10列第1行和第3行的兩個同列的灰色點,顯然在上邊界為3的時候,靠下的這個灰色點決定了右邊界最遠端,而上邊界繼續向上移動,右邊界只可能更小,因此靠上的這個灰色點其實什麼作用也沒有。因此同一列只有最下邊一個點有用。這樣讓我們在計算黑點的時候,最多隻考慮之前出現的m個點,而不是理論最壞im個點,這個優化還是很關鍵的。因此當我們列舉(i,j)名下的矩形,上邊界是xi行的時候,右邊界最遠是ry,左邊界最遠是ly,那麼組合一下就得到了(n-i+1)*(j-ly+1)*(ry-j+1)個貢獻(下邊界方案*左邊界方案*右邊界方案,上邊界已經確定是xi)。上邊講的一個跳出條件仍然有效。

整個演算法複雜度嚴格小於 mn*(n/2+m)(點數*平攤上邊界列舉數量&最多需要考慮的點),實際上跑起來是快的飛起,因為中間會跳出,而且要考慮的點沒那麼多。

注意:窩不知道為什麼。。。在函式體內部開了100個int的陣列記憶體就爆炸了。。。委屈。。

第二:這個題不需要高精度,蠻好的。。。

還有,我真該回高中好好學學再上大學,現在只能頑強地被高中生摩擦。

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 105;
int mp[MAX][MAX];
vector<pair <int,int>> Color[MAX*MAX];
int m,n; 
void input(){
	for (int i=0;i<=n*m;i++){
		Color[i].clear();
	} 
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++){
		for (int j = 1;j<=m;j++){
			scanf("%d",&mp[i][j]);
			Color[mp[i][j]].push_back(make_pair(i,j));
		}
	}
	for (int i=0;i<=n*m;i++){
		if (!Color[i].empty()){
			sort(Color[i].begin(),Color[i].end());
		}
	}
}
vector<int> yIndex[MAX];
int bottom[MAX];
long long calc(int col){
//	cout<<"Calcing "<<col<<endl;
	memset(bottom,0,sizeof(bottom));
	for (int i = 1;i<=n;i++){
		yIndex[i].clear();
	}
	long long ans = 0;
	for (auto now:Color[col]){
		int ni = now.first,nj = now.second;
//		cout<<"Looping "<<ni<<","<<nj<<endl;
		for (int i = 1;i<=m;i++){
			yIndex[i].clear();
		}
		for (int i = 1;i<=m;i++){
			if (bottom[i]){
				yIndex[bottom[i]].push_back(i);
			}
		}
		int yl=1,yr=m;
		bool br = false;
		for (int ii = ni;ii>=1;ii--){
//			cout<<"Findind "<<ii<<endl;
			for (vector<int>::iterator it = yIndex[ii].begin();it!=yIndex[ii].end();it++){
				int yy = *it;
				if (yy<nj){
					yl = max(yl,yy+1);
				}else if (yy>nj){
					yr = min (yr,yy-1);
				}else{
					br = true;
					break;
				}
			}
			if (br){
				break;
//				cout<<"Finding Break"<<endl;
			}
			ans+=(n-ni+1)*(nj-yl+1)*(yr-nj+1);
//			cout<<"Finding End:With"<<(n-ii+1)*(nj-yl+1)*(yr-nj+1)<<endl;
		}
		bottom[nj] = ni;
	}
	
	return ans;
}
double work(){
	long long ans  = 0;
	for (int i = 0;i<=n*m;i++){
		if (!Color[i].empty()){
			ans +=calc(i);
		}
	}
	long long num = n*(n+1)*m*(m+1)/4;//多謝UFO___給我提出的改進建議
//	for (int i=1;i<=n;i++){
//		for (int j = 1;j<=m;j++){
//			num+=i*j;
//		}
//	}
	double anss = ((double)ans)/num;
//	cout<<"ANS:"<<ans<<" NUM:"<<num<<" Return:"<<anss<<endl;
	return anss;
}
int main(){
	int Cas;
	scanf("%d",&Cas);
	while (Cas--){
		input();
		printf("%.9f\n",work()); 
	} 
	return 0;
}