模拟二

发布时间 2023-10-31 01:00:14作者: zhuwen

先提醒一下!!!多测不清空,爆零两行泪!!!

模拟赛二补题报告

日期:\(2023\)\(10\)\(3\) 学号:\(S11390\)

一、总分:

\(T1\) 人员借调:\(30\)

\(T2\) 计算:\(30\)

\(T3\) 智能公交:\(50\)

\(T4\) 异或和:\(0\)

共:\(110\)

二、比赛过程

  • 在第一题中,只想到了一种情况,其中有几种情况没有想到,读题也有些偏差,因此只想到了一种情况因此只得了 \(30\) 分。

  • 在第二题中,想到了预处理的优化方法,但一直搞不出来,就没有继续用这样的方法了,最后就用暴力来进行了骗分,也是骗了 $30 $ 分。

  • 在第三题中,也是没有想到正确的想法,因此也只能进行歪解骗分。用暴力双层循环的方式进行骗了 \(50\) 分。

  • 在第四题中,没有啥思路,因此只能试着写一些,但直到最后也没有做出来,就得了 \(0\) 分。

三、题目分析

\(T1\) 人员借调

本题就主要找到几个模拟条件就能够做对了。本题需要前缀和思想,算出这一阶段耗费的时间。

假设小可需要在B地处理n件事情,耗时分别为 \(a_1, a_2, ..., a_n\) 分钟。

- 如果其中有一个时间超过 \(240\)。那么就一直在那里干,最后加上待机的 \(10080\) 和往返的 \(400\) 分钟。

- 只要时间相加不超过 \(240\) 那么就在那里一直干,最后加上往返的 \(400\) 分钟。

- 如果超过的话,将所有干完加上待机的 \(10080\) 和往返的 \(400\) 分钟,和干一段加上往返的 \(400\) 分钟,进行判断,取最小值,进行输出。

正解

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

#define int long long
#define swap(a, b) a ^= b, b ^= a, a ^= b
#define pb push_back

const int N = 1e6 + 10;

int n;
int a[N];
int sum;
bool flag;
int s[N];
int w;

signed main()
{
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	for(int i=1;i<=n;i++)
	{
		if(a[i]>=240)
		{
			flag=1;
		}
	}
	if(flag)
	{
		sum=s[n]+10480;
		cout<<sum<<endl;
		return 0;
	}
	else
	{
		if(s[n]<240)
		{
			cout<<s[n]+400;
			return 0;
		}
		int sum1=s[n]+400+10080;
		int sum2=s[n]+400;
		int l=1,r=1;
		while(r<=n)
		{
			if(s[r]-s[l-1]<240)
			{
				r++;
			}
			else
			{
				sum2+=400;
				l=r;
			}
		}
		cout<<min(sum2,sum1)<<endl;
	}
    //  fclose(stdin);
    //  fclose(stdout);
    return 0;
}


\(T2\) 计算

本题因为数据范围较大,所以要进行将前 \(5e6\) 的数据,先进行预处理,求出每个数位的和与乘积。

he[i]=he[i/10]+he[i%10] //求 \(5e6\) 数位和

ji[i]=ji[i/10]+ji[i%10] //求 \(5e6\) 数位积

通过暴力枚举,时间复杂度为 \(O(m-n)\),只要枚举数位和与 \(k\),相等的,取出数位积最大值,即为答案。

正解

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

#define int long long
#define swap(a, b) a ^= b, b ^= a, a ^= b
#define pb push_back

const int N = 5e6 + 10;

int sum[N]={0,1,2,3,4,5,6,7,8,9};
int s[N]={0,1,2,3,4,5,6,7,8,9};
int n,m;
int k,t;

signed main()
{
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	for(int i=10;i<=N;i++)
	{
		sum[i]=sum[i/10]+sum[i%10];
		s[i]=s[i/10]*s[i%10];
	}
	cin>>t;
	while(t--)
	{
		int maxx=-10;
		int maxn=0;
		cin>>n>>m>>k;
		for(int i=n;i<=m;i++)
		{
			if(sum[i]==k)
			{
				if(s[i]>maxx)
				{
					maxx=s[i];
					maxn=i;
				}
			}
		}
		cout<<maxn<<" "<<maxx<<endl;
	}
    //  fclose(stdin);
    //  fclose(stdout);
    return 0;
}

\(T3\) 智能公交

本体思路就通过双指针的思想,进行加和,我们通过模拟可以知道。

  • 公交车的位置就在,\(v[cnt/2]\),的地方,此时就为最短距离的点。

求出最短距离的点后。

  • 根据双指针枚举,将这个点的左右两边的总距离进行加和,此时就为最短距离。

我们可以画个图。

这时,红色部分加上橙色部分,就为最短路径。

正解

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

#define int long long
#define swap(a, b) a ^= b, b ^= a, a ^= b
#define pb push_back

const int N = 1e6 + 10;

int n,m;
int a[N],b[N];
int v[N];
int cnt;
int sum;

signed main()
{
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>a[i]>>b[i];
		sum+=abs(b[i]-a[i]);
		v[++cnt]=a[i];
		v[++cnt]=b[i];
	}
	sort(v+1,v+cnt+1);
	int pos=v[cnt/2];
	for(int l=1,r=m*2;l<r;l++,r--)
	{
		sum+=(v[r]-v[l]);
	}
	cout<<pos<<" "<<sum<<endl;
    //  fclose(stdin);
    //  fclose(stdout);
    return 0;
}

\(T4\) 异或和 \(01\)背包

这道题也是没有任何思路啊……

但是我们将这个代码分开来看,这道题就是用了 \(dp\),背包来进行做的。但是,额,我好像也不是很会,那就给我这种蒟蒻来普及一下什么是背包吧。

背包

  • \(01\)背包

  • 完全背包

  • 多重背包

  • 分布背包

  • …………

背包问题最主要的就是搞清楚数组表示的含义。

  • \(dp[i][j]\) 集合:为从前 \(i\) 个物品中选且所耗费重量 \(<=j\) 的所有选法。从中选出的最大价值。

  • \(w[i]\) 集合: 第 \(i\) 个物品的重量。

  • \(v[i]\) 集合:第 \(i\) 个物品的价值。

  • \(dp[i-1][j]\) 集合: 不含第 \(i\) 个物品。

  • \(dp[i-1][j-w[i]]+c[i]\) 集合:含第 \(i\) 个物品。

此时我们就可以进行状态转移了,这样就可以通过 \(dp[i-1]\) 层 来划分第 \(dp[i]\) 层。我们求含 \(i\) 和不含 \(i\) 中的最大值。

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);

这个就是二维背包问题的状态转移方程。

\(01\)背包二维数组

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

#define int long long
#define swap(a, b) a ^= b, b ^= a, a ^= b
#define pb push_back

const int N = 1e6 + 10;

int n,m;
int dp[1000][1000],w[1000],c[1000];

signed main()
{
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	cin>>m>>n;
	for(int i=1;i<=n;i++)
		cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(j>=w[i])  
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
			else  	
				dp[i][j]=dp[i-1][j];
	cout<<dp[n][m]<<endl;
    //  fclose(stdin);
    //  fclose(stdout);
    return 0;
}

接着,我们来进行优化,这里优化就是将二维数组转化成一维数组。

由于 \(f[i][j]\) 是由第 \(i-1\) 层推出的,就可以用它来存储第 \(i\) 层,所以我们其实只需要一层就行了。

dp[j]=max(dp[j],dp[j-w[i]]+c[i]);

这个就是一维背包问题的状态转移方程。

原来我们的 \(j\) 是从小到大进行枚举的。但我们想要的是 \(f[i-1] [j-w[i]]\)。 此时只需要将 \(j\) 从大到小进行枚举即可,因为 \(j\) 从大到小的话,在计算 \(f[i]\) 时,就不需要进行判断是否超过背包容量。\(f[j-w[i]]\),就是第 \(i-1\) 层恰好符合状态转移方程。

\(01\)背包一维数组

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;


#define int long long
#define swap(a, b) a ^= b, b ^= a, a ^= b
#define pb push_back

const int N = 1e6 + 10;

int c[N],w[N];
int dp[N];
int n,m;
 
signed main()
{
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	cin>>m>>n;
	for(int i=1;i<=n;i++)
		cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++)
		for(int j=m;j>=w[i];j--)
			dp[j]=max(dp[j],dp[j-w[i]]+c[i]); 
	cout<<dp[m]<<endl;
    //  fclose(stdin);
    //  fclose(stdout);
    return 0;
}

\(T4\) 正解—但是我不会……

#include <bits/stdc++.h>
using namespace std;
int n, m, dp[2005][2050], num[2005][2005], dpp[2050], zz[2005];
// dp[i][j]表示看到前i个数,得到收益j至少所需要的数字个数,num[i][j]表示第i组j个数最大的收益值
vector<int> ve[2005];
int main() {
	cin >> n >> m;//n个数字,最多选m个数字 
	for (int i = 1; i <= n; i++) {//输入n个数字 
		int x, y;//x是数字大小,y是所属集合编号 
		cin >> x >> y;
		ve[y].push_back(x);//y集合的vector进入一个x 
		zz[y]++;//集合y的元素个数加一个 
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= 2047; j++) {
			dp[i][j] = 1e9;
		}
	}
	for (int zu = 1; zu <= 2000; zu++) {//遍历所有的组 ,一组一组的处理 
	
		if (zz[zu] != 0)//如果这一组的元素不为空 	
			dp[1][ve[zu][0]] = 1; //第一个数 得到这一组的第一个数的收益值通过选择这一个数做到
			  
		for (int i = 2; i <= zz[zu]; i++) {//遍历这一组剩下的数字 
		// 前i个数,得到这个组的第i个数的收益可以通过直接选择这个数本身做到 
			dp[i][ve[zu][i - 1]] = 1; 
			for (int j = 1; j <= 2047; j++) {//遍历所有可能的数字(收益值) 
				if (dp[i - 1][j] != 1e9) {//如果前i-1个数字产生这个收益所用的最小数字个数存在(不为初始值) 
				
				//前i个数产生j这个数字(收益)的数字应用个数是前i个数和前i-1个数的使用数字个数的最小值 
					dp[i][j] = min(dp[i][j], dp[i - 1][j]);
				
				//前i个数字产生j^这一组的第i个数字产生的新数字(收益)的数字使用个数是产生j的数字个数+1,和本来就有的数字个数的最小值 
					dp[i][j ^ ve[zu][i - 1]] =min(dp[i - 1][j] + 1, dp[i][j ^ ve[zu][i - 1]]);
				}
			}
		}
		
		for (int j = 1; j <= 2047; j++) {//遍历所有的收益 
			if (dp[zz[zu]][j] != 1e9)//如果这一组的数字个数对应产生j这个数字(收益),所使用的最小数字个数存在,即能异或出这个数字 
			//num数组记录这一组对应 这些数字个数 能得到的最大数字 
				num[zu][dp[zz[zu]][j]] = j;
				
		}
		for (int i = 1; i <= zz[zu]; i++) {//dp数组重新初始化一下 
			for (int j = 1; j <= 2047; j++) {
				dp[i][j] = 1e9;
			}
		}
	}
	for (int i = 1; i <= 2000; i++) {//遍历所有组 
		for (int j = m; j >= 1; j--) {//能选的数字个数最多是m个 
			for (int k = 1; k <= zz[i];k++) { // 最后的枚举,只枚举到本组的个数
				if (j >= k)//能选这个组的k个数字
				//j个数字产生的数字收益最大值要么不变,要么就是往前推k个数,从第i组选k个数字的最大收益累加 
					dpp[j] = max(dpp[j], dpp[j - k] + num[i][k]);
			}
		}
	}
	cout << dpp[m];//输出m个数字的最大收益 
}