2024/7/30训练

C. Have Your Cake and Eat It Too

题目链接添加链接描述

算法:二分+模拟+贪心

题目大意

有一块长方形蛋糕被切成了 n 块。
Alice、Bob 和 Charlie 三人要分享这些蛋糕块,每个人对每块蛋糕的价值观念不同,分别用 ai、bi 和 ci 表示。
每个人对蛋糕块的总价值 tot 是相同的。
需要将蛋糕分成三个不相交的连续子数组,分别给 Alice、Bob 和 Charlie,且每个人获得的蛋糕块价值至少是 tot / 3 的上限(即 ⌈tot / 3⌉)。
目标是找到一种分配方案,使得满足上述条件。

算法思路

对于每一次蛋糕分法,如果我们只是进行搜索的话,肯定会超时的,那我们就尽可能的贪心的想:先分配一个人的蛋糕,使得这个人满足的前提下划分的蛋糕最少,然后剩下的蛋糕再分给另一个人,同样也是满足的前提下划分的蛋糕最少,最后看剩下的蛋糕能否满足剩下那个人的要求即可。
如何实现就是比较麻烦的。
如果直接暴力的话肯定也会超时。
我们可以这样考虑:先分配A,A的起点就是第一块蛋糕,那么我们就可以通过二分搜索(+前缀和)搜索出最少满足A的蛋糕的右边界。
B的左边界就是A的右边界+1,然后同理,通过二分搜索(+前缀和)搜索出最少满足B的蛋糕的右边界。
剩下的就是C的蛋糕。
这样就有六种情况,一一枚举就好了。
但是需要注意越界的情况,也就是找不到的情况,所以每一次搜索以后都需要进行判断。

代码

#include<bits/stdc++.h>
#define ll long long
const int N = 2e5+7;
using namespace std;
ll a[N],b[N],c[N];
ll t;
bool check1(int l,int r) {
	return a[r]-a[l-1]>=t;
}
bool check2(int l,int r) {
	return b[r]-b[l-1]>=t;
}
bool check3(int l,int r) {
	return c[r]-c[l-1]>=t;
}
void slove() {
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
	int n;
	cin>>n;
	for(int i=1,tt; i<=n; ++i) cin>>tt,a[i]=a[i-1]+tt;
	for(int i=1,tt; i<=n; ++i) cin>>tt,b[i]=b[i-1]+tt;
	for(int i=1,tt; i<=n; ++i) cin>>tt,c[i]=c[i-1]+tt;
	t = ceil(1.0*a[n]/3);
	int al,ar,bl,br,cl,cr;
	//先搜索A 再搜索B
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check1(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			al = 1,ar = l;
			bl = ar + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check2(bl,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(c[n]-c[l]>=t) {
					cout<<al<<" "<<ar<<" "<<bl<<" "<<l<<" "<<l+1<<" "<<n;
					return;
				}
			}
		}
	}
	//先搜索A 再搜索C
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check1(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			al = 1,ar = l;
			cl = ar + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check3(cl,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(b[n]-b[l]>=t) {
					cout<<al<<" "<<ar<<" "<<l+1<<" "<<n<<" "<<cl<<" "<<l;
					return;
				}
			}
		}
	}
	//先搜索B 再搜索A
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check2(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			bl = 1,br = l;
			al = br + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check1(al,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(c[n]-c[l]>=t) {
					cout<<al<<" "<<l<<" "<<bl<<" "<<br<<" "<<l+1<<" "<<n;
					return;
				}
			}
		}
	}
	//先搜索B 再搜索C
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check2(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			bl = 1,br = l;
			cl = br + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check3(cl,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(a[n]-a[l]>=t) {
					cout<<l+1<<" "<<n<<" "<<bl<<" "<<br<<" "<<cl<<" "<<l;
					return;
				}
			}
		}
	}
	//先搜索C 再搜索A
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check3(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			cl = 1,cr = l;
			al = cr + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check1(al,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(b[n]-b[l]>=t) {
					cout<<al<<" "<<l<<" "<<l+1<<" "<<n<<" "<<cl<<" "<<cr;
					return;
				}
			}
		}
	}
	//先搜索C 再搜索B
	{
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (check3(1,mid)) r = mid;
			else l = mid + 1;
		}
		if(l<1 || l>n) ;
		else {
			cl = 1,cr = l;
			bl = cr + 1;
			l = l + 1;
			r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check2(bl,mid)) r = mid;
				else l = mid + 1;
			}
			if(l<1 || l>n) ;
			else {
				if(a[n]-a[l]>=t) {
					cout<<l+1<<" "<<n<<" "<<bl<<" "<<l<<" "<<cl<<" "<<cr;
					return;
				}
			}
		}
	}
	cout<<"-1";
	return;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
	cin>>t;
	while(t--) {
		slove();
		cout<<endl;
	}
	return 0;
}

C. Two Movies

题目链接添加链接描述

算法:模拟+贪心

题目大意

有一家电影公司发行了两部电影。
每部电影被 n 人观看,每个人对两部电影都有态度(喜欢、中立、不喜欢)。
需要为每部电影选择观众来留下评论,评论会影响电影的评级(喜欢则评级+1,不喜欢则评级-1,中立则不变)。
电影公司的整体评级是两部电影中评级最低的那一个。
目标是计算出在最优选择评论的情况下,电影公司可能获得的最高整体评级。

算法思路

这一题算是比较巧妙的一题,对于观众来说,无非就只有-2 , -1 , 0, 1 , 2这几种情况。
如果是-2或者2我们需要最后考虑,因为可调节性最大,只能+1或者-1,那么我们就需要最后进行这一步,把A电影和B电影的评分更接近。
如果是-1或者1的情况,那么就说明一定有一个0,那么这里我们只能贪心的考虑,有-1我们就不管,有1我们就加上。
如果是0的情况,其实和-1 1的情况基本一致,贪心的考虑把1加上即可。

所以观察以后,我们发现绝对值小的那个需要先考虑,因为没有可调节性。

代码

#include<bits/stdc++.h>
#define ll long long
const int N = 2e5+7;
using namespace std;
//	-2	-1	0	1	2
struct Node {
	short a,b;
	bool operator<(const Node& c) const {
		return abs(a+b)<abs(c.a+c.b);
	}
};
Node t[N];
int asa,asb;
void slove() {
	asa = 0, asb = 0;
	int n;
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>t[i].a;
	for(int i=1; i<=n; ++i) cin>>t[i].b;
	sort(t+1,t+n+1);
	for(int i=1; i<=n; ++i) {
		if(t[i].a+t[i].b==-2) {//都讨厌
			if(asa>asb) asa--;
			else asb--;
		} else if(t[i].a+t[i].b==-1) {//一个无感一个讨厌
			continue;
		} else if(t[i].a+t[i].b==0) {//两种情况
			if(t[i].a==1) asa++;
			if(t[i].b==1) asb++;
		} else if(t[i].a+t[i].b==1) {//一个无感一个喜欢}
			if(t[i].a==1) asa++;
			else asb++;
		} else {//都喜欢 
			if(asa<asb) asa++;
			else asb++; 
		}
	}
	cout<<min(asa,asb);
	return;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
	cin>>t;
	while(t--) {
		slove();
		cout<<endl;
	}
	return 0;
}

C. Mad MAD Sum

题目链接 添加链接描述

算法:贪心

题目大意

定义数组的 MAD(Maximum Averaged Duplicate)为数组中出现次数最多且至少出现两次的最大数字。如果没有数字至少出现两次,则 MAD 的值为 0。
给定一个数组 a,长度为 n。
初始时,设置变量 sum 为 0。
执行以下过程,直到数组 a 中所有元素都变为 0:
更新 sum,使其加上数组 a 当前所有元素的和。
找出数组 a 当前所有元素的 MAD,并让数组 a 中所有元素等于这个 MAD。
求最终过程结束后 sum 的值。

算法思路

如果这一题直接进行模拟,就会超时。
通过观察发现,每一次操作都会将一个子段变成相同的数。
然后当出现非递减的情况下,也就是数组只有若干个递增的子段构成的时候,我们每一次操作只会讲数组整体右移一位。
比如1 1 2 2 2 3 3 3
操作一次 0 1 1 2 2 2 3 3
那么我们完全可以求出当出现上述情况时,每个数字出现的对应次数。

我只考虑到了相同子段的特性,没有考虑到整体数组的特性。

代码

#include<bits/stdc++.h>
#define ll long long
const int N = 2e5+7;
using namespace std;
//第一次会将一个子段都变成相同的数
//连续字段的sum
//当出现这种状态时 只需要一直右移即可
ll sum = 0;
int a[N];
bool c[N];
void slove() {
	sum = 0;
	ll n;
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>a[i];
	int l = 1;
	for(int t=1; t<=2; ++t) {
		for(int i=1; i<=n; ++i) {
			sum+=a[i];
		}
		for(int i=1; i<=n; ++i) {
			c[i] = false;
		}
		int mx = 0;
		for(int i=1; i<=n; ++i) {
			if(c[a[i]])
				mx = max(mx,a[i]);
			c[a[i]] = true;
			a[i] = mx;
		}
	}

	for (int i = 1; i <= n; ++i)
		sum += (n - i + 1) * 1LL * a[i];
	cout<<sum;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t;
	cin>>t;
	while(t--) {
		slove();
		cout<<endl;
	}
	return 0;
}

C. Job Interview

题目链接添加链接描述

算法:双指针 + 模拟 + 贪心

题目大意

Monocarp 计划开设一家 IT 公司,需要招聘 n 名程序员和 m 名测试员。
有 n + m + 1 名候选人,编号从 1 到 n + m + 1,每位候选人有编程技能 ai 和测试技能 bi。
公司希望最大化团队技能,即程序员的编程技能总和加上测试员的测试技能总和。
Monocarp 根据候选人的编程技能和测试技能来分配职位,优先分配编程技能高的为程序员,其他为测试员。如果一个职位的名额满了,就将候选人分配到另一个职位。
需要计算在每个候选人被排除在外的情况下,团队的技能总和。
目标是确定在排除每个候选人后,团队的技能总和。

算法思路

我们可以想到第一个不参加时,我们只需要模拟后面n+m个人即可。
然后对于第二个不参加时,我们只需要在原本的基础上把第二个人去掉,第一个人放进来就行了。
重点就是代码实现。
当我们去掉一个人的同时新添加一个人,那么会有两种情况

  1. 两个人适配性一样,都适合当程序员或者测试员,那么我们完全可以让新添加的这个人代替去掉的人。
  2. 这个情况比较复杂,因为是根据时间顺序来的,那么新添加的这个人优先级很高,当我们添加这个人以后,那么这个人所在的职位就会多一个人,就需要踢掉一个人,难点就在于踢哪一个人,如果现在这个职位里面还有被分配过来的人,那么我们就可以直接踢掉第一个被分配过来的人,反正没有的话,那么就只能根据优先级的要求踢掉最后一个人。
    根据优先级的要求,先添加进来的人优先级高,那么当高优先级的人把一个职位占满以后,那么职位就无法再进人了。

情况二又会诞生一个难点,那就是如何找到第一个被分配过来的人。
直接遍历,会超时,也不能使用二分,没有单调性。
我们可以通过set或者priority_queue存储被分配过来的人,这样我们就完全实现了快速找到第一个被分配的人了。

模拟的过程我也使用了set,双指针的话实现起来更复杂,而且需要中间插值,时间复杂度上也不是也允许,而且set实现起来更简单。

代码

#include<bits/stdc++.h>
#define ll long long
const int N = 2e5+7;
using namespace std;
//可以用一个双指针表示已经排好的顺序
set<int> sta;
set<int> stb;
//ssta存储的是在sta中不完美的下标
//sstb反之
set<int> ssta;
set<int> sstb;
int a[N],b[N];
ll ans = 0;
int tx(int i) {
	if(a[i]>b[i]) return 1;
	else return 2;
}
void slove() {
	ans = 0;
	int n,m;
	cin>>n>>m;
	for(int i=1; i<=n+m+1; ++i) cin>>a[i];
	for(int i=1; i<=n+m+1; ++i) cin>>b[i];
	//先不放第一个
	for(int i=2; i<=n+m+1; ++i) {
		if(a[i]>b[i]) {
			if(sta.size()<n) {
				sta.insert(i);
				ans+=a[i];
			} else {
				sstb.insert(i);
				stb.insert(i);
				ans+=b[i];
			}
		} else {
			if(stb.size()<m) {
				stb.insert(i);
				ans+=b[i];
			} else {
				ssta.insert(i);
				sta.insert(i);
				ans+=a[i];
			}
		}
	}
	cout<<ans<<" ";
	for(int i=1; i<=n+m; ++i) {
		//排好队已经超出了范围
		if(sta.empty()) {
			if(*sstb.begin() == *stb.begin()
			        && tx(*sstb.begin())==1)
				sstb.erase(sstb.begin());
			stb.erase(stb.begin());
			ans+=b[i]-b[i+1];
		} else if(stb.empty()) {
			if(*ssta.begin() == *sta.begin()
			        && tx(*ssta.begin())==2)
				ssta.erase(ssta.begin());
			sta.erase(sta.begin());
			ans+=a[i]-a[i+1];
		}
		//如果两个人适配性一样 那么直接替换即可
		else if(tx(i)==tx(i+1)) {
			//不会出现越界的情况
			if(tx(i)==1) {
				if(*ssta.begin() == *sta.begin()
				        && tx(*ssta.begin())==2)
					ssta.erase(ssta.begin());
				sta.erase(sta.begin());
				ans+=a[i]-a[i+1];
			} else {
				if(*sstb.begin() == *stb.begin()
				        && tx(*sstb.begin())==1)
					sstb.erase(sstb.begin());
				stb.erase(stb.begin());
				ans+=b[i]-b[i+1];
			}
		} else {
			//出现适配性不一样的情况
			//优先排i 所以需要把某个数挤出去
			if(tx(i)==1) {
				ans+=a[i];
				ans-=b[i+1];
				//在sta中找到第一个不完美的下标
				// 不能通过二分实现
				// 不具有单调性
				// 可以再建立一个set来实现
//				auto it=sta.begin();
//				for(; it!=sta.end(); ++it) {
//					if(b[*it]>a[*it]) break;
//				}
//				if(it==sta.end()) --it;

				if(ssta.empty()) {
					auto it = sta.rbegin();
					ans+=(b[*it]-a[*it]);
					stb.insert(*it);
					if(tx(*it)==1)
						sstb.insert(*it);
					stb.erase(stb.begin());
					sta.erase(*it);
				} else {
					int t = *ssta.begin();
					ans+=(b[t]-a[t]);
					stb.insert(t);
					if(tx(t)==1)
						sstb.insert(t);
					stb.erase(stb.begin());
					sta.erase(t);
					ssta.erase(t);
				}
			} else {
				ans+=b[i];
				ans-=a[i+1];
//				auto it=stb.begin();
//				for(; it!=stb.end(); ++it) {
//					if(a[*it]>b[*it]) break;
//				}
//				if(it==stb.end()) --it;
				if(sstb.empty()) {
					auto it = stb.rbegin();
					ans+=(a[*it]-b[*it]);
					sta.insert(*it);
					if(tx(*it)==2)
						ssta.insert(*it);
					sta.erase(sta.begin());
					stb.erase(*it);
				} else {
					int t = *sstb.begin();
					ans+=(a[t]-b[t]);
					sta.insert(t);
					if(tx(t)==2)
						ssta.insert(t);
					sta.erase(sta.begin());
					stb.erase(t);
					sstb.erase(t);
				}
			}
		}
		cout<<ans<<" ";
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t;
	cin>>t;
	while(t--) {
		slove();
		cout<<endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值