娱乐赛第二场题解
题目难度(主观评判):
HABD<FE<CGI HABD<FE<CGIHABD<FE<CGI
主出题人qq:424579653
交流群:869585043
不会的题可以加群或加好友问。
A.BigFish的机器人工厂
💡提示1[C - Robot Factory
本题涉及的考点:模拟,排序,思维题
只需要重新排序每一位的数字(从小到大),然后特判前导0即可。
AC code:
#include <bits/stdc++.h>
using namespace std;
int n,a[10];
int main()
{
string s;
cin>>s;
n=s.size();
for(int t=1;t<=n;++t)
{
a[t]=s[t-1]-'0';
}
sort(a+1,a+1+n);
int len=0;
int t=1;
while(t<=n&&a[t]==0) ++len,++t;
for(;t<=n;++t)
{
cout<<a[t];
if(len)
{
for(int i=1;i<=len;++i) cout<<0;
len=0;
}
}
if(len) cout<<0;
return 0;
}
H.博士的源石虫统一作战
💡提示1本题涉及知识点:模拟
AC code:
#include <bits/stdc++.h>
#define int long long
#define N
#define debug(x) cout<<#x<<":"<<x<<" ";
using namespace std;
int T,n,m;
void solve()
{
string s;
cin>>n;
cin>>s;
int lenth=0;
for(int t=s.size()-2;t>=0;--t)
{
if(s[t]!=s[s.size()-1])
{
++lenth;
}
}
cout<<lenth<<endl;
}
signed main()
{
T=1;
cin>>T;
while(T--)
{
solve();
}
return 0;
}
B.BigFish的机器人工厂
💡提示1本题涉及的知识点:贪心,排序
可以想到,如果我们已经选择好一个头部零件,那么我们一定会倾向于选择一个刚好比这个头部零件略微重的身体零件来组合。这样子我们可以保证对后续选择其它的头部零件的影响尽量的小。
一种思路是利用贪心策略,从小到大排序两组零件,然后依次贪心的匹配(可以思考一下能否从大到小排序)
AC code
//找别的同学吧,之前忘保存了
D.大守护者BigFish
💡提示1本题涉及的知识点:搜索,动态规划
如果知道递归怎么写的人写这题应该和喝水一样简单了(早在比赛简介就注明了会出现搜索算法)
AC code
#include <bits/stdc++.h>
using namespace std;
int a[210],n,m,sum=0;
void dfs(int u,int num,int o)
{
if(u==m+1)
{
if(num==n)
++sum;
return ;
}
else
{
for(int i=o;i<=n&&num+i<=n;++i)
{
dfs(u+1,num+i,i);
}
}
}
inline int read()//快速读入代码,比cin更快的读入速度,但是此题没必要,仅做展示。
{
int j=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') j=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return j*x;
}
int main()
{
n=read();
m=read();
dfs(1,0,1);
printf("%d",sum);
return 0;
}
F.掀树杯
💡提示1本题涉及的知识点:二分
一道非常裸的二分题,可以结合样例解释理解题目意思。一个很朴素的思路是,最外层循环从小到大枚举当前“阻挡数”,然后内层嵌套一个循环从1到n暴力检查,时间复杂度太大,超时。考虑进行优化,注意到“阻挡数”越大,显然带走所有木材的可能性越大,所以问题具有单调性,最外层我们可以利用二分优化为log2nlog_2{n}log2n的复杂度,最终复杂度 O(nlogn)O(nlogn)O(nlogn)
如果之后有遇到有趣的题目,也欢迎进行投稿。
出题人题解:
呃呃 第一次出题写题解(⁄ ⁄•⁄ω⁄•⁄ ⁄)
所以佩佩很可爱(◍•ᴗ•◍)❤我们来看一下题目示例
T=5,N=10T=5,N=10T=5,N=10
num[i]=1,2,3,4,5,6,7,8,9,10num[i] = 1,2,3,4,5,6,7,8,9,10num[i]=1,2,3,4,5,6,7,8,9,10
如果干员阻挡数为z=15时可以把所有木头都运走,那么也能以更多的阻挡数运木头 z=16,17,18…
如果干员阻挡数为z=14时不能把所有木头都运走,那么也不能以更少的阻挡数运木头 z=13,12,11…
答案可以从单调有序中找出来,那就用二分查找把答案z猜出来。
那么接下来,就是要找出可以以z阻挡数,在T次部署内搬运完所有木头;
二分 左右边界问题
左边界来说,因为不能把一堆木材分开来搬走,所以阻挡数一定不能小于最多的那一堆木材数;
右边界来说,阻挡数若大于所有木材的数量之和,可以一次性把所有木材都搬走,再大就不利于查找了。
AC code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 判断在给定容量capacity(阻挡数)下,是否能在T次部署内带走所有木材
bool canCarry(int capacity, int T, const vector<int>& nums) {
int count = 1; // 当前部署次数,从第一次开始
int current = 0; // 当前部署已携带的木材数量
for (int num : nums) {
// 如果当前部署还能装下这堆木材
if (current + num <= capacity) {
current += num; // 携带这堆木材
} else {
//否则 需要重新部署一次
count++; // 增加部署次数
current = num; // 新部署开始携带这堆木材
// 如果已经超过允许的部署次数T,返回false
if (count > T) return false;
}
}
// 检查最终部署次数是否不超过T
return count <= T;
}
int main() {
int T, N;
cin >> T >> N;
vector<int> nums(N);
int max_num = 0, sum = 0;
// 读取所有木材堆的数量
for (int i = 0; i < N; i++) {
cin >> nums[i];
max_num = max(max_num, nums[i]); //这里时就确定二分左边界,因为阻挡数小于最多的一堆木材将肯定不能搬走
sum += nums[i];
}
// 二分查找最小可行阻挡数(开区间写法)
int left = max_num - 1, right = sum + 1;
while (left + 1 < right) {
int mid = left + (right - left) / 2; // 计算中间值,防止溢出,(left+right)/2
// 测试阻挡数为mid时是否可行
if (canCarry(mid, T, nums)) {
right = mid; // 可行,尝试更小的阻挡数
} else {
left = mid; // 不可行,需要更大的阻挡数
}
}
// 输出最低阻挡数(此时right就是最小可行阻挡数)
cout << right;
return 0;
}
E.BigFish 的奇偶变换挑战
💡提示1本题涉及的知识点:思维题
此题是一道cf线上比赛原题,作为赛时C题,如果能在一个小时的时间解决,那就已经达到很多学长的水平了。
首先,注意到,要使两个正数之和最大化,而它们的乘积必须保持不变,那么最佳的方法就是尽可能减少其中一个数(同时使另一个数最大化)。所以我们可以得到一个推论:
题目等价于,找到一个kkk,在保证 a∗k+b/ka*k+b/ka∗k+b/k 是偶数的同时,kkk尽量大。
因为题目限制偶数,所以我们先分类讨论 aaa , bbb 和 kkk 的奇偶性:
-
aaa 、 bbb 都是偶数:在这种情况下,无论选择哪一个 kkk,a∗ka*ka∗k都会是偶数,所以我们不能去选择一个 kkk 使得 bk\frac{b}{k}kb 是奇数,因此这里最大的 kkk 是 b2\frac{b}{2}2b 。
-
aaa 是偶数,而 bbb 是奇数:在这种情况下,答案不可能是偶数,所以答案是 −1-1−1 。
-
aaa 和 bbb 都是奇数: kkk 最大选 bbb 。而且无论选择哪一个 kkk , a∗k+b/ka*k+b/ka∗k+b/k 都是偶数。
-
aaa 是奇数,而 bbb 是偶数:注意到,如果 bbb 不是 444 的倍数(即代码中 b/2b/2b/2 不是 222 的倍数),这种情况下是不可能为偶数,为 −1-1−1,否则 kkk 取 b2\frac{b}{2}2b
AC code
#include <bits/stdc++.h>
#define N 200005
#define int long long
using namespace std;
int T,a,b;
void solve()
{
scanf("%lld%lld",&a,&b);
if(a%2==0&&b%2==1)
{
printf("-1\n");
return ;
}
if(a%2==1&&b%2==1)
{
printf("%lld\n",(long long)a*b+1);
return ;
}
if(a%2==1&&b%2==0)
{
int u1=b/2;
if(u1%2==1)
{
printf("-1\n");
return ;
}
printf("%lld\n",(long long)a*b/2+2);
return ;
}
if(a%2==0&&b%2==0)
{
int u1=b/2;
printf("%lld\n",(long long)a*b/2+2);
return ;
}
return ;
}
signed main()
{
scanf("%lld",&T);
while(T--)
{
solve();
}
return 0;
}
C.休息法案
💡提示1此题涉及的知识点:枚举,二分,前缀和
首先想到最最最朴素的解法,即第一层循环枚举lll,第二层循环枚举rrr,在确定的(l,r)(l,r)(l,r)范围内,第三层循环统计a,ba,ba,b个数是否满足题意。复杂度O(n3)O(n^3)O(n3)
接着我们思考如何优化解法,首先我们可以利用前缀和快速统计区间(l,r)(l,r)(l,r)内,a,ba,ba,b的个数,这样子我们可以优化成O(n2)O(n^2)O(n2),依然通过不了此题。
随后我们可以发现,满足条件的区间显然有单调性,可以发现如果确定了lll,那么只满足题意大于等于 AAA 的区间是[k1,n][k_1,n][k1,n]。只满足题意小于 BBB的区间一定是[l,k2][l,k_2][l,k2],所以最后的答案就是两个区间取交集。至于如何找到这两个区间可以利用二分,时间复杂度O(nlogn)O(nlogn)O(nlogn)
AC code
#include <bits/stdc++.h>
#define int long long
#define N 300005
using namespace std;
int n,x,y;
int a[N],b[N];
string s;
signed main()
{
cin>>n>>x>>y;
cin>>s;
s=" "+s;
vector<int> v;
v.push_back(0);
for(int t=1;t<=n;++t)
{
b[t]=b[t-1];
a[t]=a[t-1];
if(s[t]=='b') ++b[t];
else ++a[t];
}
int ans=0;
for(int t=1;t<=n;++t)
{
auto ita=lower_bound(a+1,a+1+n,x+a[t-1]);
auto itb=lower_bound(b+1,b+1+n,y+b[t-1]);
int posa=ita-a,posb=itb-b;
if(posa==n+1) break;
if(posa<posb) ans+=posb-1-posa+1;
}
cout<<ans;
return 0;
}
G.N剑霜寒十六州
💡提示1此题涉及的知识点:差分,思维题
我们知道,如果增量不是一个递增变化的等差数列,而是一个常数的话,那么我们就可以用熟悉的差分来维护。对于等差数列差分,我们需要进行两次差分操作来维护。
| 初始 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|---|---|---|---|---|---|---|---|---|---|
| l | r | ||||||||
| 单次操作后 | 0 | s | d-s | 0 | 0 | 0 | -d-e | e | 0 |
| 第一次算前缀和后 | 0 | s | d | d | d | d | -e | 0 | 0 |
| 第二次算前缀和后 | 0 | s | s+d | s+2d | s+3d | s+4d | s+5d-e = 0 | 0 | 0 |
其实真正的思考过程应该是上边这个表格从下往上思考,我们发现我们需要进行的操作在单次差分后,还是无法维护,所以我们再进行一次差分,最后发现可以维护了。而由于差分和前缀和互为逆运算,所以进行两次前缀和操作就能得到我们想要的具体值。
AC code
#include <bits/stdc++.h>
#define N 10000007
#define int long long
using namespace std;
int n,m;
int a[N];
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m;
for(int t=1;t<=m;++t)
{
int s,e,l,r,d;
cin>>l>>r>>s>>e;
if(l==r) d=0;
else d=(e-s)/(r-l);
a[l]+=s;
a[l+1]+=(d-s);
a[r+1]+=(-d-e);
a[r+2]+=e;
}
for(int t=1;t<=n;++t)
{
a[t]+=a[t-1];
}
for(int t=1;t<=n;++t)
{
a[t]+=a[t-1];
}
int maxn=-1e18,sum=0;
for(int t=1;t<=n;++t)
{
sum^=a[t];
maxn=max(maxn,a[t]);//找最大值不需要排序
}
cout<<sum<<' '<<maxn;
return 0;
}
I.BigFish的糖果分配计划v2.0
💡提示1本题涉及的知识点:gcd,贪心,思维题
题中要求最大的可能大糖果个数。我们考虑贪心的选择,首先我们对小朋友需要的糖果个数从小到大排序,对于最小个数的糖果,考虑全部为大糖果,之后依次减少糖果个数。(可以证明这个贪心策略具有决策包容性,因此是正确的),具体实现参考代码。
AC code
#include <bits/stdc++.h>
#define N 200005
#define int long long
#define debug(x) cout<<#x<<":"<<x<<' ';
using namespace std;
int a[N];
int n,x,y;
signed main()
{
cin>>n>>x>>y;
for(int t=1;t<=n;++t)
{
cin>>a[t];
}
sort(a+1,a+1+n);
int sum;
sum=a[1];
int u1=0,u2=a[1];
int u11=gcd(x,y),ax=y/u11,ay=x/u11;
int cha=ax-ay;
for(int t=2;t<=n;++t)
{
if((a[t]-a[t-1])%cha!=0)
{
sum=-1;
break;
}
int u=(a[t]-a[t-1])/cha;
u1+=ax*u;
u2-=ay*u;
if(u2<0)
{
sum=-1;
break;
}
sum+=u2;
}
cout<<sum;
return 0;
}
1287

被折叠的 条评论
为什么被折叠?



