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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值