CCF 201809-4 试题名称: 再卖菜

参考博客:201809-4 再卖菜 ccf 剪枝优化

    题目就不贴了。

题目要求:给出商店的第二天的菜价,第二天的菜价是第一天临近商店的价格的平均值(求平均时使用去尾法求整),要求找到符合要求的第一天菜价中字典序最小的一种。下面分别使用 a , b a,b a,b表示第一天,第二天的菜价,由题意可得,菜价应满足下列不等式:(因为价格是整数,所以将等式右边的值减一,则可将原来的 &lt; &lt; < 换为 ≤ \le
2 b 1 ≤ a 1 + a 2 &lt; 2 ( b 1 + 1 )   →   2 b 1 ≤ a 1 + a 2 ≤ 2 b 1 + 1   2b_1 \le a_1+a_2 &lt; 2(b_1+1) \ \rightarrow \ 2b_1 \le a_1+a_2 \le 2b_1+1 \ 2b1a1+a2<2(b1+1)  2b1a1+a22b1+1  3 b 2 ≤ a 1 + a 2 + a 3 ≤ 3 b 1 + 2 3b_2 \le a_1+a_2+a_3 \le 3b_1+2 3b2a1+a2+a33b1+2 3 b 3 ≤ a 2 + a 3 + a 4 ≤ 3 b 3 + 2 3b_3 \le a_2+a_3+a_4 \le 3b_3+2 3b3a2+a3+a43b3+2 ⋅ ⋅ ⋅ ··· ⋅ ⋅ ⋅ ⋅ ···· 3 b n − 1 ≤ a n − 2 + a n − 1 + a n ≤ 3 b n − 1 + 2 3b_{n-1} \le a_{n-2}+a_{n-1}+a_{n} \le 3b_{n-1}+2 3bn1an2+an1+an3bn1+2 2 b n ≤ a n − 1 + a n ≤ 2 b n + 1 2b_{n} \le a_{n-1}+a_{n} \le 2b_{n}+1 2bnan1+an2bn+1
思路一:纯dfs(80分) :现在约束已经有了,等式左右两边的值都是已知的,最直接的想法就是从小到大枚举 a 1 a_1 a1可能的取值(要求字典序最小所以对于任意 a i a_i ai的取值都应从小到大枚举), a 1 a_1 a1确定后再根据第一个不等式考虑 a 2 a_2 a2可能的取值, a 2 a_2 a2确定后考虑 a 3 a_3 a3····,这样不断进行下去,最后如果 a n a_n an的值也满足要求,则当前的序列就是所需的字典序最小的序列。
思路二:dfs+剪枝(100分):第一个方法的问题在于每次尝试都要计算到第n天或者遇到中间某天没有可能的取值时才能得出本次尝试的结果,当n比较大时就会因为可能的情况太多而超时。为了减少尝试的次数,可以使用记忆化剪枝的方法。
    具体来说就是使用变量isfind表示是否已找到最小字典序序列,用visit[i][curr][pre] 数组来表示 第i天的菜价设为curr,第i-1天设为pre 这种情况是否已经尝试过,如果尝试过就直接返回(不再进行尝试),没有尝试过才执行后面的。下面说一下为什么这种方法有效,一开始可能会认为为了求字典最小的序列我们的尝试的取值都是从小到大的,怎么可能存在尝试过的再一次被尝试?这个问题的答案就是:我们尝试第i天的取值时的确是从小到大进行尝试,当第i天的取值为a时,对于第i+1,i+2两天,假设它们的取值为b,c,假设这种尝试不满足题目要求,函数会返回(此时isfind的值为false),接下来第i天的值被设为a+1,对于第i+1,i+2天,假设它们又分别被设置为了b,c,这次我们就能确定这种尝试是没有必要的,因为之前尝试过了,并没有找到可行的解(如果之前找到可行解程序已经退出),所以可以不用再进行尝试,通过这种方式就能较少尝试的次数,从而避免超时。

下面是两种思路的代码。

思路一代码(80分):

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

int d1[305],d2[305];
int n; 

// 第i天的价格设为value是否可能 
bool isValid(int i,int value)
{
	d1[i] = value;
	if(i == n) 
	{
		return d1[n-1] + d1[n] >= 2 * d2[n] && d1[n-1] + d1[n] < 2 * (d2[n] + 1);
	}
	else if(i == 1)
	{
		for(int v = 2*d2[1]-d1[1];v < 2*(d2[1]+1)-d1[1]; v++)
		{
			if(v >= 1 && isValid(2,v))
				return true;
		}
	}
	else
	{
		for(int v = 3*d2[i]-d1[i-1]-d1[i];v< 3*(d2[i]+1)-d1[i-1]-d1[i]; v++)
			if(v >= 1 && isValid(i+1,v))
				return true;
	}
	return false;
}

void solve()
{
	for(int v = 1;v < 2*(d2[1]+1); v++)
	{
		if(isValid(1,v))
			break;
	}
}

int main(int argc, char const *argv[])
{
	cin >> n;
	for(int i = 1;i <= n; i++)
		scanf("%d",&d2[i]);

	solve();
	
	for(int i = 1;i <= n; i++)
		printf("%d ",d1[i]);
	
	cout << endl;
	return 0;
}

思路二代码(100分):

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

const int maxn = 400;
int n;
int day1[maxn],day2[maxn];
bool visit[maxn][maxn][maxn];
bool isfind = false;

void dfs(int i,int curr,int pre)
{
	if(visit[i][curr][pre])
		return ;
	
	visit[i][curr][pre] = 1;
	day1[i] = curr;
	
	if(i == n)
	{
		if((curr + pre) / 2 == day2[n])
		{
			isfind = true;
			return ;
		}
	}
	else
	{
		int next = day2[i]*3 - curr - pre;
		for(int k = 0;k < 3; k++)
		{
			if(next+k >= 1 && !isfind)
				dfs(i+1,next+k,curr);
				
		}
	}
	
}

int main(int argc, char const *argv[])
{
	cin >> n;
	for(int i = 1;i <= n; i++)
		cin >> day2[i];
	
	memset(visit,0,sizeof(visit));
	
	for(int v1 = 1;v1 < 2*day2[1]; v1++)
	{
		day1[1] = v1;			
		dfs(2,2*day2[1]-v1,day1[1]);
		if(!isfind)
			dfs(2,2*day2[1]-v1+1,day1[1]);
		else
			break;
	}
	for(int i = 1;i <= n; i++)	
		cout << day1[i] << " ";
	cout << endl;
	
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值