【UVA1289】Stacking Plates

Description

The Plate Shipping Company is an Internet retailer that, as their name suggests, exclusively sells plates. They pride themselves in offering the widest selection of dinner plates in the universe from a large number of manufacturers.

In a recent cost analysis the company has discovered that they spend a large amount of money on packing the plates for shipment. Part of the reason is that plates have to be stacked before being put into shipping containers. And apparently, this is taking more time than expected. Maybe you can help.

A shipment of plates consists of plates from several manufacturers. The plates from each manufacturer come stacked, that is, each arranged in a single stack with plates ordered by size (the smallest at the top, the largest at the bottom). We will call such a stackproperly ordered. To ship all these plates, you must combine them into a single stack, again properly ordered. To join the manufacturers' stacks into a single stack, two kinds of operations are allowed:


  • Split: a single stack can be split into two stacks by lifting any top portion of the stack and putting it aside to form a new stack.
  • Join: two stacks can be joined by putting one on top of the other. This is allowed only if the bottom plate of the top stack is no larger than the top plate of the bottom stack, that is, the joined stack has to be properly ordered.


Note that a portion of any stack may never be put directly on top of another stack. It must first be split and then the split portion must be joined with the other stack. Given a collection of stacks, you have to find the minimum number of operations that transforms them into a single stack. The following example corresponds to the sample input, and shows how two stacks can be transformed to a single stack in five operations:

\epsfbox{p6036.eps}

Input

Each test case starts with a line containing a single integer n (1$ \le$n$ \le$50), the number of stacks that have to be combined for a shipment. This is followed by n lines, each describing a stack. These lines start with an integer h (1$ \le$h$ \le$50), the height of the stack. This number is followed by h positive integers that give the diameters of the plates, from top to bottom. All diameters are at most 10 000. These numbers will be in non-decreasing order.

Output

For each test case, display the case number and the minimum number of operations (splits and joins) that have to be performed to combine the given stacks into a single stack.

Sample Input

2
3 1 2 4
2 3 5
3
4 1 1 1 1
4 1 1 1 1
4 1 1 1 1

Sample Output

Case 1: 5
Case 2: 2
題解
一道神奇的dp.由於最後從上到下的一堆,所以可以先離散,原數據是10000的嘛。
假設我們進行了x次split操作; 則 我們總共需要的操作次數爲x+x+n-1 因爲進行一次split操作,連通塊的個數就會遞增1; 進行x次的話,就有x+n塊; 然後把這x+n合併成一塊的話; 需要x+n-1次join操作; 再加上x次split操作;總共就是2*x+n-1次操作; 然後我們考慮最後所有的方塊都疊成了一塊的情形 假設,我們一開始把n堆的每一堆都染成同一種顏色; 則最後只剩一堆方塊的時候,肯定是不同顏色相間的;設最後只剩一堆方塊的時候,上下相鄰的單個方塊之間,顏色不同的對數爲cnt; 則答案就爲2*cnt-n+1 爲什麼不是上面分析的2*x+n-1次操作? 因爲這裏的cnt並不是分離的次數; 想一想,如果沒有進行分離操作,直接全部疊在一個上面,肯定也有n-1個對是不相等的顏色; 則我們求出的cnt如果想變成分離的次數應該減去2*(n-1) 
即2*(cnt-2*(n-1))+n-1 = 2*cnt-n+1 這樣,就能根據最後答案的那一疊方塊知曉答案了; (也可以參考代碼上寫的理解方式)這可以給我們的DP提供思路; 
我們就對最後的答案的那一堆進行DP; 在DP之前;我們先將所有的盤離散化一下; 
因爲盤的直徑最大達到了10000; 直接枚舉盤的直徑可能會跨了很多; 而盤子的數目最多爲2500 然後,同一堆裏的盤子,如果它的直徑是一樣的,那就把它們都當成一個盤子就好了; 之後; 考慮最後的答案構成的一堆盤子; 肯定是從上到下,盤子的直徑不下降的; 則我們,從上到下,依次枚舉每個盤子,它要放在哪一堆上面; 設f[i][j]表示,直徑小於等於i(已經離散化過)的盤子,把它們都放到第j堆盤子上,最小的相鄰不同顏色對數; 對於f[i][]這個狀態肯定是移動到已經有i這個盤子的堆上比較划算; 設爲j 則轉移的時候; 只要找有i-1這個盤子的堆k; 然後用f[i-1][k]這個狀態轉移到f[i][j] 算一下會增加多少個相鄰不同顏色就好; 轉移的過程實際上就是其他i盤子移動到那個j上,以及k那一堆i-1盤子之上的整個移動到i上面來 (注意如果k那一堆的i-1盤子下面是i盤子的話,可以省掉一個不同的對數) j和k相不相同要分類討論,因爲是兩種情況; 因爲允許放在“空地”上,所以j和k相同的情況也是ok的; 最後在f[cnt][1..n]中找最小值; 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 100000000
using namespace std;
const int maxn=2501; 
pair<int,int >a[maxn];
int cnt[maxn],f[maxn][maxn],h[maxn][maxn];
//f[i][j]表示將前i個數放在第j堆所要切得最小刀數 
int c,m,t,n,index1,ans;
void prepare(){
	int p;
	sort(a+1,a+c+1);
	c=unique(a+1,a+c+1)-a-1;
	for(int i=1;i<=c;i++){
		p=i;++m;
		while(a[p].first==a[p+1].first&&p<c){
			p++;
		}
		for(int j=i;j<=p;j++){
			h[m][a[j].second]=1;
		}
		cnt[m]=p-i+1;
		i=p;
	}
}
int dp(){
	memset(f,127/3,sizeof(f));
	for(int i=1;i<=n;i++){
		f[1][i]=cnt[1]-h[1][i];
	}
	for(int i=2;i<=m;i++){
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				if((j!=k||cnt[i]==1)&&h[i][k]==1)
				f[i][j]=min(f[i][j],f[i-1][k]+cnt[i]-1);//一個i連着k這堆一起移 
				else f[i][j]=min(f[i][j],f[i-1][k]+cnt[i]);
			}
			if(h[i][j]==0) f[i][j]++;//如果j這堆有i這個盤子,就需要多移一步。 
		}
	}
	ans=inf;
	//假設每一堆盤子都有一個不可移動的大盤子 
	for(int i=1;i<=n;i++){
		ans=min(ans,f[m][i]-n+1);//所以要減去(n-1)刀 
	}
}
int main(){
	while(scanf("%d",&n)==1){
		memset(cnt,0,sizeof(cnt));
		memset(h,0,sizeof(h));
		m=c=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&t);
			for(int j=1;j<=t;j++){
				scanf("%d",&a[++c].first);
				a[c].second=i; 
			}
		}
		prepare();
		dp();
/*		for(int i=1;i<=n;i++)
		cout<<f[m][i]<<endl;*/
		printf("Case %d: %d\n",++index1,ans*2+n-1);
	}
	
	return 0;
}

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