12.12基础个人赛(补题)
B题Brightness Begins
题意:
有n个电灯泡,在初始时全都是亮着的,接下来,每次操作i可以改变编号为i的倍数的灯泡的状态(开变关,关变开)。现在有k个灯泡是亮着的,问需要的最少灯泡数n是多少。
思路:
首先分析条件,操作i可以改变编号为i的倍数的灯泡,因此当第i个灯泡有奇数个数时,该灯泡会关闭(初始是1),偶数则打开。
因此,问题更新为求第k个数的非完全平方数是多少,这里结合一个数学理论:1~n中有|sqrt(n)|个完全平方数。即n-sqrt(n)=k,
所以通过打表可以得到最终的公式n=k+sqrt(k)
code:
#include <bits/stdc++.h> using namespace std; const int N=1e6+10,M=1e3+10,inf=INT_MAX; int t; //注意数据范围 long long k; int main(){ ios::sync_with_stdio(0),cin.tie(0);cout.tie(0); cin >>t; while(t--) { cin >>k; cout <<k+(long long)(sqrtl(k)+0.5)<<"\n"; } return 0; }
E题Green and Black Tea
题意:
有n包茶,a包是绿茶(G),b包是黑茶(B),对茶进行组合排序,使得连续相同的茶不能超过k包。
注:每包茶只能使用一次。
思路:
本题属于构建模拟题,首先可以得知,要使茶包的连续个数最小,肯定要使用最小的茶包个数b,对另一茶包a进行分割,分割次数越多,区间长度越小,所以每次数据能达到的最优茶包长度为a/(b+1),且因为茶包不能舍弃,所以最终区间长度d为ceil(a/(b+1))。
由此可得,当d的长度大于k时,排列无法构建,而符合时,即可开始构建序列。
code:
#include <bits/stdc++.h> using namespace std; const int N=1e6+10,M=1e3+10,inf=INT_MAX; int n,k,a,b; //定义字符,避免写重复代码 char x='G',y='B'; int main(){ ios::sync_with_stdio(0),cin.tie(0);cout.tie(0); cin >>n>>k>>a>>b; if (a<b) { //swap():交换括号内的两个值 swap(a,b); swap(x,y); } //假设每次取1个b进行阻隔,最多会有b+1个区间,相除后向上取整即可得到a每段区间最大的值 int res=ceil(1.0*a/(b+1)); //当每段的值超过k时,此时无法构建字符 if (res>k)cout <<"NO\n"; else { //取a以最大个数排列的次数 int ans=a%(b+1); //ans=0,可以都以最大个数排完 int tmp=ans>0?1:0; while(ans--) { //每次跑,a的长度减res for (int i=1;i<=res;i++)cout <<x; a-=res; //以1个b进行阻隔 if (b) { cout <<y; b--; } } //如果ans有值,将接下来的排列个数减1 res-=tmp; //将a和b剩余的值跑完 while(a>0 || b>0) { for (int i=1;i<=res;i++)cout <<x; a-=res; if (b) { cout <<y; b--; } } cout <<"\n"; } return 0; }
G题AGAGA XOOORRR
题意:
数组a中有n个数,每次操作可以将两个相邻的数异或,求能否将数组中的值都变为相同的值,且数组个数至少要有两个。
思路:
本题考验对异或的理解,首先异或的值为,同为0,不同为1,由此可以得到当对整个数组进行异或和后,如果数组为0,,则必然存在两个值是相同的,但还有一种情况,即如果相同的值为奇数个,此时异或和值也不为0,但依然符合条件,所以需要重新遍历数组,每当值与异或的最终值相同时,保留该值,重新开始记数。
code:
#include <bits/stdc++.h> using namespace std; const int N=1e6+10,M=1e3+10,inf=INT_MAX; int t,n; int a[N]; int main(){ ios::sync_with_stdio(0),cin.tie(0);cout.tie(0); cin >>t; while(t--) { int ans=0; cin >>n; for (int i=1;i<=n;i++) { cin >>a[i]; ans^=a[i]; } //当所有数异或后为0时,此时肯定存在两个数相同 if (!ans)cout <<"YES\n"; else { //当相同个数为奇数时,最后异或出来的结果也不为0 //因此对状态重新计数 int res=0,cnt=0; for (int i=1;i<=n;i++) { res^=a[i]; //当异或和等于最终异或值时 if (res==ans) { //奇数相加 cnt++; res=0; } } //当个数大于等于2时,说明符合条件 if (cnt>=2)cout <<"YES\n"; else cout <<"NO\n"; } } return 0; }
H题Passing the Message
题意:
有t组数据,给你n个孩子的身高的数组a,找每个孩子的左信使和右信使的下标。
左信使的条件:
- a[i]不能越过比它高的孩子去看其他人
- 信使是a[i]左边能看到的人里最高的
- 找不到信使则输出0
右信使与左信使条件相同,只是左右互换
思路:
本题可以使用单调栈,通过维护数组的形式找出每个单边的值,跑两遍数组就可以解决了。
code:
#include <bits/stdc++.h> using namespace std; const int N=1e6+10,M=1e3+10,inf=INT_MAX; struct Index{ int num,id; }; int t,n; int a[N],l[N],r[N]; int main(){ ios::sync_with_stdio(0),cin.tie(0);cout.tie(0); cin >>t; for (int text=1;text<=t;text++) { //清空数组 memset(l,0,sizeof(l)); memset(r,0,sizeof(r)); cin >>n; for (int i=1;i<=n;i++)cin >>a[i]; stack<Index>q; //从左到右,维护从大到小的单调栈,找出左信使 for (int i=1;i<=n;i++) { Index now; //在栈空之前,找出比它矮的人 while(q.size()) { now=q.top(); if (now.num>a[i])break; //越往前数值将会越大 l[i]=now.id; q.pop(); } //每次存入的点都必然是比前一个点小的 q.push({a[i],i}); } //清空栈 while(q.size())q.pop(); //从右到左,维护从大到小的单调栈,找出右信使 for (int i=n;i>=1;i--) { Index now; //以下同理 while(q.size()) { now=q.top(); if (now.num>a[i])break; r[i]=now.id; q.pop(); } q.push({a[i],i}); } cout <<"Case "<<text<<":\n"; for (int i=1;i<=n;i++)cout <<l[i]<<' '<<r[i]<<"\n"; } return 0; }
H题Stones
题意:
T和A轮流进行取石子的游戏,有n个石子和k个取出个数,每次拿取都要从k个拿取个数中选择一种进行拿取。
注:拿取个数时从小到大排的。
思路:
本题属于博弈+dp,最重要的就是推转移方程,状态方程怎么来的,已经在代码注释中给出,不做过多描述。
code:
#include <bits/stdc++.h> using namespace std; const int N=1e6+10,M=1e3+10,inf=INT_MAX; long long n,k; long long a[N],dp[N]; /* 条件: T和A轮流拿取a[i]的石头 状态: dp[i]:石头数为i时,能拿取的最大值 T先取数量变为i-a[j] A后取,此时A能取到的最大为dp[i-a[j]] 所以游戏进行一轮时,T得到的收益为i-[j]+dp[i-a[j]]+a[j] 即i-dp[i-a[j]] */ int main(){ ios::sync_with_stdio(0),cin.tie(0);cout.tie(0); cin >>n>>k; for (int i=1;i<=k;i++)cin >>a[i]; //先手能取的最大值 for (int i=1;i<=n;i++) { for (int j=1;j<=k;j++) { if (i<a[j])break; dp[i]=max(dp[i],i-dp[i-a[j]]); } } cout <<dp[n]<<"\n"; return 0; }