重庆交通大学程序设计竞赛集训队暑假第二次练习赛——题解

题目来源及知识点

A:第四届SCPC中南民族大学程序设计竞赛-A.Funny卖锅盔(栈)

B:蓝桥杯练习系统.试题.算法训练.搬走要石(二分)

C:背包九讲.多重背包问题(多重背包的二进制优化/单调队列优化)

D:重庆交通大学第八届程序设计竞赛-D.走台阶问题(模拟)

E:2021年ICPC国际大学生程序设计竞赛暨陕西省第九届大学生程序设计 竞赛-I.Rabbit(模拟)

Problem.A 卖烧饼

本题的保温盒其实抽象出来就是个栈。可以用STL提供的的stack容器,也可以用数组模拟栈。O2优化时显然数组模拟更快,在O2优化下两者运行速度相差无几基本上不会因为这一点卡时间,在这里简单介绍一下数组模拟栈的实现:

1.int stk[N], tp = 0; //tp:栈顶的下标 
2.tp = 0; //初始化
3.stk[++tp] = x; //添加
4.tp--; //弹出栈顶元素 
5.stk[tp]; //取栈顶元素
6.tp == 0 ?; //查询是否为空

回到题目本身,这题要注意的时十七不希望看到的情况有哪些:

  • 烧饼有剩余
  • 有同学没买到烧饼
  • 保温盒装不下

前两条也就决定了如果输入的两个字符串长度不等那么最终结果一定是NO,而综合所有信息,YES只有一种可能,那就是每个人都买到想要的烧饼且烧饼刚好全卖完。而剩下的就是不断模拟的操作了。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

const int N = 2e6 + 10;
string str1, str2;
//保温盒:数组模拟栈|tp表示栈顶 
int tp, stk[N];
int n, m;

void solve()
{
	cin >> n >> m;
	cin >> str1;
	cin >> str2;
	int i = 0, flag = 0;
	//如果两个字符串长度不同直接NO 
	if (n != str2.length())
	{
		cout << "NO" << endl;
		return;
	}
	for (int j = 0; j < str2.length(); j++)
	{
		//如果保温盒顶上的刚好是该同学想要的则出栈 
		if (tp!=0&&str2[j] == stk[tp])
		{
			tp--;
			continue;
		}
		else
		{
			//入栈操作 
			while (i < n && tp <= m && str2[j] != str1[i])
			{
				stk[++tp] = str1[i++];
				continue;
			}
			//此时已经走到尽头或者保温盒装不下了 
			if (i == n || tp > m)
			{
				flag = 1;
				break;
			}
			i++;
		}
	}
	if (flag) cout << "NO" << endl;
	else cout << "YES" << endl;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	solve();
}

Problem.B 搬货物

这题是以二分思路去解的,因为题目已经告诉了最终结果在0-106之间,整数二分有两种思路:

//将区间[begin,end]分为[begin,mid]和[mid+1,end] 
int bsearch_1(int begin, int end)
{
	while (begin < end)
	{
		int mid = (begin + end) / 2;
		//int mid = begin + (end - begin) / 2;
		if (check(mid))
			end = mid;
		else
			begin = mid + 1;
	}
	return begin;
}

//区间[begin,end]分为[begin,mid-1]和[mid,end] |注意此时mid的选取 
int bsearch_2(int begin, int end)
{
	while (begin < end)
	{
		int mid = (begin + end + 1) / 2;
		//int mid = begin + 1 + (end - begin) / 2;
		if(check(mid))
			begin = mid;
		else
			end = mid - 1; 
	}
	return begin;
}

注释的那行int mid是为了防止计算过程中爆int,当然直接开long long就可以,但这样的写法可以在部分面试题等无法开longlong的场合使用。

这题二分的思路就是在0-1e6中先找到中值mid,判断能否每次使用mid的体力完成搬货物操作,依次二分,即要使用的是上面的第一种二分法。

check函数的实现就是要尽量多的把连续货物捆绑在一起搬,也就是一个最大部分前缀和的思想,找出最佳的搬法。要注意的在check函数里面至少要搬一次,这也是为什么cnt从1开始。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

const int N = 1010;
int n, m, a[N];

//二分 
bool check(int mid)
{
	//cnt:搬动次数,sum:一部分重量之和 
	int cnt = 1, sum = 0;
	for (int i = 0; i < n;)
	{
		if (sum + a[i] <= mid)
		{
			sum += a[i];
			i++;
		}
		else if (a[i] > mid)//若某件货物质量大于了mid,直接返回false 
			return false;
		else
		{
			cnt++;
			sum = 0;
		}
	}
	return cnt <= m;
}

void solve()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	int l = 0, r = 1e6, mid;
	//整数的二分,分为[l,mid]和[mid+1,r]两部分 
	while (l < r)
	{
		mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	solve();
}

Problem.C 烧饼店搬迁啦

这题第一眼一看就是一个多重背包问题,多重背包的大致思路如下:

for (int i = 1; i <= n; i++)
    for (int j = 0; j <= m; j++)
        for (int k = 0; k <= s[i] && k * v[i] <= j; k++)
            f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);

但要注意的是这种朴素做法面对这题的数据量是会TLE的,本题就可以用二进制优化(也可以用单调队列优化,如果将V,mi,wi和si的范围再扩大十倍那么二进制优化也会TLE,那时就需要考虑单调队列优化),这里给出二进制优化的解法,单调队列的方案就留给大佬补充了。

二进制优化就是一种捆绑方案,将第i件物品以2^{0},2^{1},2^{2},...,2^{k}s[i]-2^{0}-2^{1}-2^{2}-...-2^{k}件(s[i]-2^{0}-2^{1}-2^{2}-...-2^{k}>0s[i]-2^{0}-2^{1}-2^{2}-...-2^{k}-2^{k+1}<0),此时,无论选s[i]范围内的任意数量的该物品,都能用上面的组合凑出来。

例如有10件物品i,我们可以分为1,2,4,此时已经可以凑出1-7的任意数量,只需要再补一个3即可凑出1-10任意数量物品的物品i。又例如200件物品i,就可以用1,2,4,8,16,34,64,73来凑出1-200任意数量物品的物品i。

而通过二进制捆绑后,每个捆绑体视作独立个体,那么就可以转化为01背包问题,重点过程就是二进制分割的过程。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

//多重背包的二进制优化 
const int N = 12010;
int v[N], w[N], f[N], n, m;

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--)
	{
		memset(v, 0, sizeof(v));
		memset(w, 0, sizeof(w));
		memset(f, 0, sizeof(f));
		cin >> n >> m;
		int a, b, s;
		int cnt = 0, sum = 0;
		//将同类物品以1,2,4……捆绑 
		for (int i = 1; i <= n; i++)
		{
			cin >> a >> b >> s;
			sum += b * s;
			int k = 1;
			while (k < s)
			{
				cnt++;
				v[cnt] = k * a;
				w[cnt] = k * b;
				s -= k;
				k *= 2;
			}
			if (s > 0)
			{
				cnt++;
				v[cnt] = s * a;
				w[cnt] = s * b;
			}
		}
		//转化为01背包问题 
		for (int i = 1; i <= cnt; i++)
		{
			for (int j = m; j >= v[i]; j--)
			{
				f[j] = max(f[j], f[j - v[i]] + w[i]);
			}
		}
		cout << sum - f[m] << endl;
	}
}

Problem.D 填满豆腐盒

纯纯找规律的签到题,类斐波那契数列,普通递归会TLE,别忘记开longlong。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

//找规律题,其实就是类斐波那契数列 
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t, m;
	cin >> t;
	while (t--)
	{
		cin >> m;
		if (m <= 2)
		{
			cout << m << endl;
			continue;
		}
		int a = 1, b = 2, c;
		for (int i = 3; i <= m; i++)
		{
			c = a + b;
			a = b;
			b = c;
		}
		cout << c << endl;
	}
}

Problem.E 农场的兔子

因为出题人是陕西人所以就找了道5月底刚刚过去的陕西省赛的签到题(真心是省赛签到题)。

本题中可爱值为1对最后结果没有影响,0乘以任何数都得0。所以输入得序列中若只有1个0则初始得可爱值为0,唯一可能改变可爱值的情况就是去掉这个0。若输入序列有两个以上的0,那么无论去点哪一只兔子,可爱值永远是0。而若输入的序列没有0的情况下,去掉一只兔子能够使可爱值发生改变的只能是去掉可爱值不为1的一只兔子。那么变化后的情况数量就是初始序列(没有0的条件下)非1元素的种类数。

因此,在输入序列不含0的情况下本题可以用set集合来维护可爱值非1的兔子的种类数。将可爱值非1的兔子插入集合,那么集合的大小就是可爱值非1的兔子的种类数,也就是此时的结果。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

//使用集合set维护不同可爱值的兔子 
set<int> rab;
int n, cute;

void solve()
{
	cin >> n;
	int s = 0;
	while (n--)
	{
		cin >> cute;
		if (cute == 1) continue; //可爱值为1对乘积无影响 
		else if (cute == 0) s++; //统计可爱值为0的兔子个数 
		else rab.insert(cute); //将可爱值非0,1的兔子用集合维护 
	}
	if (s >= 2) cout << 0 << endl; 
	else if (s == 1) cout << 1 << endl;
	else cout << rab.size() << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	solve();
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值