Codeforces Div2 2016.08.29 C題

原題目:http://www.codeforces.com/contest/711/problem/C

(翻譯如下:)

【題目名稱】給樹上色

【時限】2s

【空間限制】256M

【題目描述】

有兩位好朋友到達了一個公園。公園裏生長了n棵樹(被編號爲1~n)。他們決定將公園裏的樹塗上顏色。

起初,第i號樹擁有初始顏色c[i]。這兩位好朋友發現,公園裏的樹總共只有m種不同的顏色。所以0<=c[i]<=m,當c[i]=0時,意味着這棵樹是沒有初始顏色的。

兩位朋友決定,只給沒有初始顏色的樹上色(也就是說,只爲c[i]=0的樹上色)。他們可以從編號爲1~m的m種顏色中,選取一種來給樹塗色。爲第i棵樹塗上第j種顏色需要花費代價p[i][j].

他們定義了樹的美麗值:

將1~n號樹,分成若干段,要求每一段的樹顏色都相同。這樣分得的段數的最小值,就是樹的美麗值。例如,當樹的顏色依次爲:2,1,1,1,3,2,2,3,1,3時,樹的美麗值是7,因爲我們可以將這10棵樹最少分爲7段:{2},{1,1,1},{3},{2,2},{3},{1},{3}.

他們想給所有沒有初始顏色的樹塗上顏色,使得這些樹的美麗值恰好爲k.你的程序需要幫助他們求出達成目標的最小塗色代價。當然,也有可能無法達成目標。

注意,他們不能給有初始顏色的樹上色。

【輸入格式】

第一行,三個整數n,m,k.(1<=k<=n<=100,1<=m<=100).

第二行,n個整數c[1],c[2],…,c[n],(0<=c[i]<=m),表示樹的初始顏色。當c[i]=0時,表示i號樹無初始顏色,當c[i]不爲0時,表示第i號樹的初始顏色爲c[i].

第3~n+2行,每行m個正整數,其中第i+2行的第j個數表示給第i號樹塗上第j種顏色的代價p[i][j].擁有初始顏色的樹,也會有相應的塗色代價,不過儘管如此,你仍然不能爲它們塗色。(1<=p[i][j]<=10^9).

【輸出格式】

一個整數.如果能夠達成目標,輸出最小塗色代價;如果不能達成目標,輸出-1.

Examples
input
3 2 2
0 0 0
1 2
3 4
5 6
output
10
input
3 2 2
2 1 2
1 3
2 4
3 5
output
-1
input
3 2 2
2 0 0
1 3
2 4
3 5
output
5
input
3 2 3
2 1 2
1 3
2 4
3 5
output
0
【樣例說明】參見原題目.

【題解】

這是一道動態規劃題。通常我們可以定下狀態f[i][j],表示使前i棵樹的美麗值爲j需要的最小代價。不過我們發現這個狀態並不方便轉移,它根本沒有體現顏色帶來的影響。

於是我們決定多加一維,記錄第i號樹的顏色。於是用f[i][j][p]表示使得前i棵樹美麗值爲j,且第i棵樹顏色恰好爲p的最小塗色代價。

現在寫轉移方程。

如果第i棵樹有初始顏色,那麼沒有辦法,我們必須使用它原本的顏色。於是轉移方程爲:

f[i][j][p]=minn{minn{f[i-1][j-1][q]},f[i-1][j][p]}.(1<=q<=m,q!=p).

什麼意思呢?首先我們可以與第i-1棵樹不同色,這樣美麗值會+1,或者我們也可以與第i-1棵樹同色,這樣美麗值不變。

如果第i棵樹沒有初始顏色,我們就需要枚舉1~m的所有顏色,作爲i號樹的顏色,狀態轉移方程與上面類同,只是多一次循環。

現在來考慮效率,枚舉i,j已有2層循環,如果樹有初始顏色,那麼只需再枚舉最小值,共3層循環;如果樹沒有初始顏色,我們需要枚舉結尾顏色,同時還要枚舉對應的最小值,共4層循環。時間複雜度O(n*k*m^2).n,k,m<=100,

於是運算量是10^8級別的。好在常數不大,Codeforces的評測機跑得飛快,於是108ms就過了。

【參考代碼1】

#include<cstdio>
#define ll long long 
#define inf 0x7ffffffffffffff
int n,m,k;
ll price[110][110],f[110][110][110];
int color[110];
ll minn(ll a,ll b)
{
	return a<b?a:b;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=k;j++)
		{
			for(int t=1;t<=m;t++)
			{
				f[i][j][t]=inf;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&color[i]);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
		 	scanf("%I64d",&price[i][j]);
		}
	}
	ll Minn;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i&&j<=k;j++)
		{
			Minn=inf;
			if(color[i]!=0)
			{
				for(int p=1;p<=m;p++)
				{
					if(p!=color[i])
					{
						Minn=minn(Minn,f[i-1][j-1][p]);
					}
				}
				f[i][j][color[i]]=minn(Minn,f[i-1][j][color[i]]);
			}
			else
			{
				for(int t=1;t<=m;t++)
				{
					Minn=inf;
					for(int p=1;p<=m;p++)
					{
						if(p!=t)
						{
							Minn=minn(Minn,f[i-1][j-1][p]);
						}
					}
					f[i][j][t]=minn(Minn,f[i-1][j][t])+price[i][t];
				}
			}
		}
	}
	bool flag=0;
	ll ans=inf;
	for(int i=1;i<=m;i++)
	{
		if(f[n][k][i]<100000000000)
		{
			flag=1;
			ans=minn(ans,f[n][k][i]);
		}
	}
	if(flag)
	{
		printf("%I64d",ans);
	}
	else
	{
		printf("-1");
	}
	return 0;
}

———————————————————————————————————————————————————

不過我們還是有必要考慮一下優化。對於最小值,我們是否可以預處理,以避免每次都枚舉一遍。我們可以記下一個Minn[i][j][t]數組,它表示f[i][j][p](1<=p<=m,p!=t)的最小值。然而你會發現上當了,因爲有了這個數組,我們的確不用去枚舉一遍找最小值,但是維護這個數組,有需要多出一層循環,時間複雜度沒有變化,反而增大了常數。

———————————————————————————————————————————————————

我們只用Minn[i][j]表示f[i][j][p](1<=p<=m)的最小值是否可行?你會發現,這無法解決同色與異色的問題。於是我們想到,可以記錄最小值與次小值(可以等於最小值)。如果最小值與當前顏色的值不等,我們直接選用它即可,因爲這可以保證一定不同色。如果最小值與當前顏色的值相等,我們取次小值。而每次更新也是O(1)的複雜度。總時間複雜度是O(n*k*m),10^6級別。不過好像這樣寫常數增大了不少,在Codeforces上跑了一下,31ms過。比裸的暴力動規還是要快不少。但沒有想象的那麼快,也許是我代碼寫得太醜了。

【參考代碼2】

#include<cstdio>
#define ll long long 
#define inf 0x7ffffffffffffff
int n,m,k;
ll price[110][110],f[110][110][110],MINN[110][110][2];
int color[110];
ll minn(ll a,ll b)
{
	return a<b?a:b;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=k;j++)
		{
			for(int t=1;t<=m;t++)
			{
				f[i][j][t]=inf;
			}
			MINN[i][j][0]=MINN[i][j][1]=inf;
		}
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&color[i]);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
		 	scanf("%I64d",&price[i][j]);
		}
	}
	ll Minn;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i&&j<=k;j++)
		{
			Minn=inf;
			if(color[i]!=0)
			{
				if(MINN[i-1][j-1][0]!=f[i-1][j-1][color[i]])
				{
					Minn=MINN[i-1][j-1][0];
				}
				else
				{
					Minn=MINN[i-1][j-1][1];
				}
				f[i][j][color[i]]=minn(Minn,f[i-1][j][color[i]]);
				if(f[i][j][color[i]]<=MINN[i][j][0])
				{
					MINN[i][j][1]=MINN[i][j][0];
					MINN[i][j][0]=f[i][j][color[i]];
				}
				else if(f[i][j][color[i]]<MINN[i][j][1])
				{
					MINN[i][j][1]=f[i][j][color[i]];
				}
			}
			else
			{
				for(int t=1;t<=m;t++)
				{
					if(MINN[i-1][j-1][0]!=f[i-1][j-1][t])
					{
						Minn=MINN[i-1][j-1][0];
					}
					else
					{
						Minn=MINN[i-1][j-1][1];
					}
					f[i][j][t]=minn(Minn,f[i-1][j][t])+price[i][t];
					if(f[i][j][t]<=MINN[i][j][0])
					{
						MINN[i][j][1]=MINN[i][j][0];
						MINN[i][j][0]=f[i][j][t];
					}
					else if(f[i][j][t]<MINN[i][j][1])
					{
						MINN[i][j][1]=f[i][j][t];
					}
				}
			}
		}
	}
	bool flag=0;
	ll ans=inf;
	for(int i=1;i<=m;i++)
	{
		if(f[n][k][i]<100000000000)
		{
			flag=1;
			ans=minn(ans,f[n][k][i]);
		}
	}
	if(flag)
	{
		printf("%I64d",ans);
	}
	else
	{
		printf("-1");
	}
	return 0;
}

# When Who Problem Lang Verdict Time Memory
20293256 2016-08-31 06:30:12 19991202lym C - Coloring Trees GNU C++11 Accepted 31 ms 10500 KB

 
 
 
 
My contest submissions
 
 
# When Who Problem Lang Verdict Time Memory
               
20250771 2016-08-29 16:36:50 19991202lym C - Coloring Trees GNU C++11 Accepted 108 ms 10300 KB

發佈了31 篇原創文章 · 獲贊 6 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章