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个人即可。
然后对于第二个不参加时,我们只需要在原本的基础上把第二个人去掉,第一个人放进来就行了。
重点就是代码实现。
当我们去掉一个人的同时新添加一个人,那么会有两种情况
- 两个人适配性一样,都适合当程序员或者测试员,那么我们完全可以让新添加的这个人代替去掉的人。
- 这个情况比较复杂,因为是根据时间顺序来的,那么新添加的这个人优先级很高,当我们添加这个人以后,那么这个人所在的职位就会多一个人,就需要踢掉一个人,难点就在于踢哪一个人,如果现在这个职位里面还有被分配过来的人,那么我们就可以直接踢掉第一个被分配过来的人,反正没有的话,那么就只能根据优先级的要求踢掉最后一个人。
根据优先级的要求,先添加进来的人优先级高,那么当高优先级的人把一个职位占满以后,那么职位就无法再进人了。
情况二又会诞生一个难点,那就是如何找到第一个被分配过来的人。
直接遍历,会超时,也不能使用二分,没有单调性。
我们可以通过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;
}