hiho一下 第141周

题目1 : 自行车架 传送门

时间限制: 5000ms
单点时限: 1000ms
内存限制: 256MB

描述

小Hi的宿舍楼下有一块用于停自行车的区域。平时自行车都停得非常杂乱,于是楼长打算去买一排自行车架用来停车。自行车架一般有P个槽,每个槽的两侧都可以停入自行车;但是一个槽位同时只能有一侧停入自行车。此外,停入一辆自行车会导致无法在这一侧的附近若干个槽位中停入自行车。

经过调查,这栋宿舍楼的学生共拥有N辆A型自行车、M辆B型自行车和K辆C型自行车。其中A型自行车会导致这一侧的左右各1个槽位不能使用,B型自行车会导致这一侧的左右2个槽位不能使用,C型自行车会导致这一侧的左右3个槽位不能使用。

现给定N、M和K,楼长希望知道P至少要是多少,才能将所有自行车都停入。

如图中所示,P最少为7就可以存放下2辆A型,1辆B型和2辆C型。

输入

每个输入文件包含多组测试数据,在每个输入文件的第一行为一个整数Q,表示测试数据的组数。

每组测试数据为3个整数N、M和K,意义如前文所述。

对于20%的数据,满足0<=N、M、K<=2,Q=100

对于40%的数据,满足0<=N、M、K<=50,Q=100

对于100%的数据,满足0<=N、M、K<=50,1<=Q<=100000

输出

对于每组测试数据,输出一个整数P,表示自行车架至少需要的槽位。

样例输入
4
2 1 2
1 0 0
2 0 2
1 2 2
样例输出
7
1
5
7
题目解析:传送门

这是一道比较复杂的DP题目。

首先我们观察题目数据,可以发现N、M和K都小于等于50,所以总共只有51^3大约是125000种情况。而Q的范围是100000,这提示我们应该预先把所有情况的答案都算出来保存在ans[51][51][51]里,遇到一个查询就直接输出。

那么怎么求ans[i][j][k]呢? 考虑到这是一个多阶段的决策问题,我们首先想到的就是看看能不能DP。对于ans[i][j][k],枚举最后一辆自行车是ABC哪种类型,转移到ans[i-1][j][k]、ans[i][j-1][k]和ans[i][j][k-1]这些状态上去。

遗憾的是我们仔细分析一下就会发现如果状态中只保存3种类型自行车的数量,是没办法确定转移的下一个状态的。

举个例子,比如我们知道ans[2][1][2]=7(题目中的例子),我们想在此基础上添加一辆B型车,那么这时ans[2][2][2]应该是多少呢? 我们发现如果我们不知道最右的类型,我们就不能判断车架长度需要+2还是+3。

所以我们在状态中再加入两维,f[i][j][k][x][y],表示i个A型,j个B型,k个C型,第一排最后一个是x型,第二排最后一个是y型(x和y的取值范围都是{A, B, C}),并且第一排长度大于第二排时车架的最短长度。

遗憾的是我们再仔细分析一下就会发现我们还是没办法确定转移的下一个状态。原因时我们不知道第二排最后一辆车距离第一排最后一辆车有多远。

于是我们在状态中再加入一维,f[i][j][k][x][y][d],其中最后一维表示第一排和第二排最后一辆车的距离差。值得说明的是,d的取值范围只有{1, 2, 3}。d不能等于0,因为一个槽位同时只能有一侧停入自行车。d不会超过3,因为如果d大于等于4的话,我们一定可以把第一排最后一辆车放到第二排,使得总长度减少。

最终我们得到了一个5维的状态,这时总算是可以确定转移的下(上)一个状态了。

对于f[i][j][k][x][y][d],我们最后添加的一辆车可能是第一排的的x型号,也可能是第二排的y型号;同时最后添加的车的左边那一辆可能有3种类型。所以总共f[i][j][k][x][y][d]的前驱状态有6个。

代码:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e8+10;
int ans[52][52][52][4][4][4];

int DP( )
{
	memset ( ans, maxn , sizeof(ans));
	ans[1][0][0][0][0][3] = 1;
	ans[0][1][0][1][0][3] = 1;
	ans[0][0][1][2][0][3] = 1;
	for (int i = 0; i < 51 ; ++i)
	{
		for (int j = 0; j < 51 ; ++j)
		{
			for (int k = 0; k < 51 ; ++k)
			{
				for (int l = 0; l < 3 ; ++l)
				{
					for (int m = 0; m < 3 ; ++m)
					{
						for (int n = 1; n < 4 ; ++n)
						{
							if ( ans[i][j][k][l][m][n] != maxn )
							{
								for (int i1 = 0; i1 < 3 ; ++i1)
								{
									int fi = i, fj = j, fk = k;
									switch ( i1 )
									{
										case 0 : fi++;
											break;
										case 1 : fj++;
											break;
										case 2 : fk++;
											break;
									}
									int tmplmn = n + max ( l, i1 ) + 2 ;
									if ( tmplmn > 3 )
										tmplmn = 3;
									ans[fi][fj][fk][i1][m][tmplmn] = min ( ans[fi][fj][fk][i1][m][tmplmn], ans[i][j][k][l][m][n] + max(l, i1) + 2 );
									tmplmn = max ( max (m, i1) + 2 - n, 1 );
									if ( tmplmn > 3 )
										tmplmn = 3;
									ans[fi][fj][fk][i1][l][tmplmn] = min ( ans[fi][fj][fk][i1][l][tmplmn], ans[i][j][k][l][m][n] + tmplmn );
								}
							}
						}
					}
				}
			}
		}
	}
}

int main ()
{
	//freopen ("F:\\CSLeaning\\Thinking in C++\\hihocoder\\in.in", "r", stdin);
	
	int t, n, m, l, res;
	cin>>t;
	DP ();
	ans[0][0][0][0][0][1] = 0;
	while( t-- )
	{
		cin>>n>>m>>l;
		res = maxn;
		for (int i = 0; i < 3; ++i)
		{
			for (int j = 0; j < 3; ++j)
			{
				for (int k = 1; k < 4; ++k)
				{
					res = min (res, ans[n][m][l][i][j][k] );
				}
			}
		}
		cout<<res<<endl;
	}
	//std::cout << "Hello, World!" << std::endl;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值