POJ2185_Milking Grid_KMP_二維求最小覆蓋子矩陣

【不能用最小公倍數或最大值求覆蓋子矩陣的寬和高!!】


昨天看了很久這個題,去POJ 的論壇上,看到幾個分析說網上大多數的AC代碼都是錯的,只是POJ上的數據過於弱就過了,比如求行方向上最小覆蓋的列數(寬度)只要用到求最小公倍數的就都是錯的,希望大家做此題的時候注意一下,下面題解的時候會解釋爲什麼是錯的



題意:


給一個二維的字符串矩陣,求他的一個最小的子矩陣,使得無限個當前子矩陣的疊加可以覆蓋此二維矩陣。(不知道這樣說明易不易懂)

【換個說明方法:就是說這個最小子矩陣的平行疊加可以覆蓋母矩陣,但是並不要求恰好覆蓋,舉例

ABABA
ABABA
這樣一個矩陣,AB就是最小的子矩陣

ABABAB

ABABAB當六個此矩陣平行疊加時,原矩陣被覆蓋在其中】


如圖,藍色爲母矩陣,每個黃色的小方塊是子矩陣,子矩陣的平行疊加可以覆蓋母矩陣,但不需要是恰好覆蓋,只要將母矩陣包含其中就可以了


題解:

1.首先可以證明,如果存在一個子矩陣滿足要求,則起點在左上角的同等大小的子矩陣也滿足要求。【自己領會一下】

2.【解釋爲什麼求最小公倍數是錯的】先看網上大部分代碼的思路,求出橫向每一行的最小的覆蓋子串,長度分別是L1、L2......Ln,然後求n個數的最小公倍數就是最小寬度,如果最小公倍數大於母矩陣的寬,就讓母矩陣的寬作爲最小寬度

上面這種求法的寬度固然是一種解,但很有可能不是最小的,因爲你每行都取最小的覆蓋子串長度,但還有其他的滿足條件的比較長的子串也滿足要求,這時也許用其他的子串長度反而能取到更小的結果:

比如

兩行的母矩陣,列寬爲13,第一行的最小子串長度爲3,但是4,5,6也滿足要求,第二行的最小子串長度爲4,此時若按照最小公倍數求法,3、4的最小公倍數爲12,但是如果第一行不取最小子串,而是取更大的子串4,此時兩行都是4,那隻需要寬度爲4 的子矩陣就能滿足要求了,遠遠低於之前的12

再比如,舉個實例:

2 8
ABCDEFAB
AAAABAAA

第一行子串寬度6,8都可,第二行子串最小爲5,但是6,7,8也可以滿足要求,這時取6,6的組合就遠比取6,5的組合要好

但是這裏我並不否認求最小公倍數這一思路,但是用最小公倍數的方法的話,就需要把所有情況的最小公倍數都求出來,取最小的,而不是每行取最小寬度再求公倍數就是最小的了,所以按照正確的最小公倍數解法,上面這個實例需要把(6,5),(6,6),(6,7),(6,8),(8,5),(8,6),(8,7),(8,8)這可能的8種情況的最小公倍數都求出來,再求最小值,就是正解了


3.但是由上可得,我有一萬行,每行10種可能的話,那就是10自乘10000次的情況,求最小公倍數顯然TLE,其實大可不必求最小公倍數,而是對於每一行,有一個值(或多個)使得所有行取這個值都滿足要求,這個最小值即爲所求,看圖:

這個是上面那個實例的矩陣圖,值爲一表示取當前長度的子串可以覆蓋當前行,可以看到6,8都可以使得兩行同時滿足,那6更小,我們就取6

4.這樣我們就得到了求行方向上最小寬度的方法:

開一個int型數組times[],用來記錄當前長度i在所有行中滿足條件的次數,(換種解釋方法就是:每當i滿足可以覆蓋當前行時,times[i]++;)當遍歷完所有行數後,times[i]==行數 的最小的i就是我們求的結果。用上面的實例就是times[]={0,0,0,0,1,2,1,2}當times[i]==2時,即爲所求


但是如何求當前行中所有的情況呢,我們知道,對於母串str,長度爲n,【strlen(str)-next[n]】就是最小的滿足條件的子串長度,此時講next[]數組回溯,設一個變量j,初值設爲n,每次讓j=next[j],這樣strlen(str)-next[j]就是所有的情況了,直到next[j]==0,回溯停止

爲什麼這樣可以呢?這是由next[]數組本身的性質決定的,具體的原因跟我在上一篇的博客【POJ2752】中的解釋很相像,只需要再加一步是當 前綴等於後綴時(不一定是最長前綴),對應一個可求重複子串的長度,大家看那一篇中的解釋再自己思考一下就很容易理解了,博文地址:http://blog.csdn.net/u011343989/article/details/9318057


5.那麼橫向求完,縱向的如何求呢?

其實一旦解決了一維,另一維就直接可以用裸的KMP算法求解了,所以縱向的可以按照一維的情況來求,此時需要先將矩陣優化一下,只取我們上面求得的寬度width的矩陣,這樣將二維壓縮成一維,但是每個元素都是寬度爲width寬的字符串,這時比較兩元素相等的條件就不是str[pos-1]==str[cnd]了,而是strcmp(str[pos-1],str[cnd])==0,即比較兩字符串是否相等,這時當把縱向的next數組求完,行數爲n的話,最短的高度就是n-next[n],就求得我們需要的高度height了

最後所求子矩陣的大小就是height*width



題目:

Milking Grid
Time Limit: 3000MS
Memory Limit: 65536K
Total Submissions: 4833
Accepted: 2009

Description

Every morning when they are milked, the Farmer John's cows form a rectangular grid that is R (1 <= R <= 10,000) rows by C (1 <= C <= 75) columns. As we all know, Farmer John is quite the expert on cow behavior, and is currently writing a book about feeding behavior in cows. He notices that if each cow is labeled with an uppercase letter indicating its breed, the two-dimensional pattern formed by his cows during milking sometimes seems to be made from smaller repeating rectangular patterns.

Help FJ find the rectangular unit of smallest area that can be repetitively tiled to make up the entire milking grid. Note that the dimensions of the small rectangular unit do not necessarily need to divide evenly the dimensions of the entire milking grid, as indicated in the sample input below.

Input

* Line 1: Two space-separated integers: R and C

* Lines 2..R+1: The grid that the cows form, with an uppercase letter denoting each cow's breed. Each of the R input lines has C characters with no space or other intervening character.

Output

* Line 1: The area of the smallest unit from which the grid is formed

Sample Input

2 5
ABABA
ABABA

Sample Output

2

Hint

The entire milking grid can be constructed from repetitions of the pattern 'AB'.

Run ID User Problem Result Memory Time Language Code Length Submit Time
11779814 chengtbf 2185 Accepted 956K 94MS C++ 1733B 2013-07-14 09:48:49

代碼:

(個人覺得自己寫的還是比較優化的)


#include<stdio.h>
#include<string.h>

char str[10005][77];
int next_row[80];//用來存行方向上的next數組
int next_column[10005];//用來存列方向上的next數組
int times[77];//用來記錄所有行中存在長度爲i的覆蓋子串的個數,當(i==總行數)時表明所有的行數都存在長度爲i的覆蓋子串,即爲所求
//而i是通過next數組逆序得到的,i=strlen(str)-next[j]。而遞歸j=next[j]直到j<=0;跟之前的一個next[]回溯題的方式是一樣的
int n,m;//分別是n行,m列
int ans;
int width;
int height;

void get_next_row(int x)//建立每一行的next數組
{
	int pos=2;int cnd=0;
	memset(next_row,0,sizeof(next_row));
	next_row[0]=-1;
	next_row[1]=0;
	while (pos<=m)
	{
		if (str[x][pos-1]==str[x][cnd])
		{
			cnd++;
			next_row[pos]=cnd;
			pos++;
		}
		else if(cnd>0)
		{
			cnd=next_row[cnd];
		}
		else
		{
			next_row[pos]=0;
			pos++;
		}
	}

}
void get_next_column()
{
	int pos=2;int cnd=0;
	memset(next_column,0,sizeof(next_column));
	next_column[0]=-1;


	for (int i = 0; i <n ; i++)
	{
		str[i][width]='\0';//此處添加\0的作用是爲了下面壓縮維度進行比較做準備
	}
	while (pos<=n)
	{
		if (strcmp(str[pos-1],str[cnd])==0)
		{
			cnd++;
			next_column[pos]=cnd;
			pos++;
		}
		else if(cnd>0)
		{
			cnd=next_column[cnd];
		}
		else
		{
			next_column[pos]=0;
			pos++;
		}
	}
}


int main()
{
	int i,j;
	char temp;
	while (scanf("%d%d",&n,&m)!=EOF)
	{
		memset(times,0,sizeof(times));
		for ( i = 0; i <n; i++)
		{
			scanf("%c",&temp);
			scanf("%s",str[i]);
			get_next_row(i);
			j=m;
			times[j]++;//任何情況下整個矩陣本身都是一種解
			while (next_row[j]>0)
			{
				times[m-next_row[j]]++;//表明長度爲m-next_row[j]的子串是符合條件的覆蓋子串
				j=next_row[j];
			}
		}
		for ( i = 1; i <=m ; i++)
		{
			if (times[i]==n)
			{
				width=i;break;
			}
		}
		get_next_column();
		height=n-next_column[n];
		printf("%d\n",width*height);

	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章