目录
总结一下,暑假的最后一场萌新联赛,打的一般,虽然a了5道题,但罚时太高了,这点也跟前面的状态有关,前两个小时我都是很落后的,状态很不好,简单的签到题也卡了半天。。。后面慢慢清醒了,能做出来的都做了,最后5min的时候还a了一道题,不枉费我改了又改。暑假集训的这一个多月,也是越来越松懈怠慢,慢慢的没有了学习的激情,可能是要学的东西越来越难了,或者是快开学了,所以最后坚持这几天了,加油↖( ^ ω ^ )↗
L-整数商店的购物之旅
解题思路
这道题很简单,但一开始没有思路,想偏了就越想越复杂。所以一开始的思考方向还是很重要的,这道题N的范围只有1e9,而且给了你一个式子,且未知数只有N,那就很显然了,能购买到的最大整数N,也就是查找最大的满足
<=X,即可。所以不难想到用二分来查找这个数。
AC代码
cpp
int a,b,x;
int check(int mid)
{
if(a*mid+b*((int)log10(mid)+1)<=x)
return 1;
else
return 0;
}
void solve()
{
cin>>a>>b>>x;
int l=0,r=1e9,f=0;
while(l<r)
{
int mid=(l+r+1)/2;
if(check(mid))
{
f=1;
l=mid;
}
else
r=mid-1;
}
if(!f)cout<<0<<endl;
else
cout<<l<<endl;
}
E-数字支配
解题思路
其实就是让我们输出【1,n】之间的字典序最大的序数的序列。可以想到,一定是让9开头最好,所以只需要构造一个字符串,使他的长度是原字符串长度-1,并且由9填充。这样就能保证字符即不超过n又是字典序最大的。但是有一种特殊情况,那就是如果出现91,993,9998这样的数字,其实输出原字符串是更优的,所以只需要再比较一下构造的这个字符s1和原字符s的大小(按字典序比较),如果s>s1就输出s,否则输出是s1.
AC代码
cpp
void solve()
{
cin>>s;
int n=s.size();
if(n==1)
{
cout<<s<<endl;
return ;
}
string s1;
for(int i=1;i<n;i++)
s1+='9';
if(s1<s)
cout<<s<<endl;
else
cout<<s1<<endl;
}
I-外卖大战
解题思路
这道题没什么方法,按照题目的意思去模拟,注意好关键点就好,可以用ABC记录优惠力度,A1,B1,C1记录记录订单数量,s1,s2,s3记录没有选择的连续人数,然后按要求遍历就行。
AC代码
cpp
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int A=0,B=0,C=0,s1=0,s2=0,s3=0,A1=0,B1=0,C1=0;
for(int i=1;i<=n;i++)
{
if(A>=a[i])A++,A1++,s1=0,s2++,s3++;
else if(A<a[i]&&B>=a[i])B++,B1++,s2=0,s1++,s3++;
else if(A<a[i]&&B<a[i]&&C>=a[i])C++,C1++,s3=0,s1++,s2++;
else s1++,s2++,s3++;
if(s1>=3)A+=2,s1=0;
if(s2>=3)B+=2,s2=0;
if(s3>=3)C+=2,s3=0;
}
cout<<A1<<" "<<B1<<" "<<C1<<endl;
}
K-还在分糖果!
解题思路
拿走了所有包含数字7的数字,那么说明1~10中,只包含9个数字,10 ~ 20中只包含9个数字...不难想到看作九进制来计算,每个数的9进制数,不就是拿走7后的数的进位方式吗,但是要注意的是,9进制转换时,n%9一定只包含数字0 ~ 8,但我们要拿走的数字是7,那么影响的是7之后的数字,所以如果a=n%9>=7的话要将结果+1,通过样例也可以很容易的看出来。
AC代码
cpp
void Jin(int n)
{
string s;
while(n)
{
int a=n%9;
if(a>=7)a++;
s+=(a+'0');
n/=9;
}
reverse(s.begin(),s.end());
cout<<s<<endl;
}
void solve()
{
cin>>n;
Jin(n);
}
D-穿过哈气之门
解题思路
这道题让求最多的满足条件的区间的个数,我们不难想到用滑动窗口去做,即节省时间又高效,维护一个满足包含所有 m 种元素类型的区间,可以用双指针维护左右端点来记录区间,一开始左边界为L=1,右边界R暂定,循环遍历过程中同时记录包含的种类数ans,当ans=m的时候,更新一下区间数量sum+=(n-r+1),因为[1,r]满足那么r后面的每增加一个数构成的区间一定也满足。
AC代码
cpp
int n,m,a[N];
unordered_map<int,int>mp;
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
int ans=0,sum=0,k=1;
deque<int>q;
for(int i=1;i<=n;i++)
{
if(mp[a[i]]==0)ans++;
mp[a[i]]++;
while(ans==m)
{
sum+=(n-i+1);
mp[a[k]]--;
if(mp[a[k]]==0)ans--;
k++;
}
}
cout<<sum;
}
J-凹包
凸包的学习
在做这道题前,先了解下凸包
什么是凸包简单说,给定平面上的点集,凸包是将最外层点连接成的凸多边形,能包裹所有点。
求凸包常用两种算法:Graham算法和Andrew算法,它们都得对点排序,时间复杂度都是O(nlogn) ,效率不错。这里咱们聚焦Andrew算法。
Andrew算法详解
- 主要步骤
- 排序:先把所有点按x坐标为第一关键字、y坐标为第二关键字排序。排序后,第1个和最后1个点(第n个点)肯定在凸包上,这是后续操作的基础。
- 求下凸包:顺序枚举所有点,用栈维护当前在凸包上的点。新点入栈前,得判断栈顶的点要不要弹出。怎么判断呢?计算新点与栈顶两个点构成的有向直线的位置关系。要是新点处在由栈顶两点构成的有向直线的右侧或者共线,就把栈顶旧点弹出,直到不能弹了,新点再入栈。这一步是为了保证凸包的"凸性",把凹进去的点给过滤掉。
- 求上凸包:逆序枚举所有点,同样用栈维护。过程和求下凸包类似,也是通过判断点的位置关系,保证凸包的形状。要注意,每个点入栈最多两次,出栈也不超两次,总操作次数不超过4n ,效率有保障。
- 代码展示
cpp
struct Point{double x,y;} p[N],s[N]; // 定义点的结构体,p存所有点,s存凸包上的点
int n,top; // n是点的数量,top是栈顶指针
// 计算叉积,判断点的位置关系
double cross(Point a,Point b,Point c){
return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x);
}
// 计算两点距离
double dis(Point a,Point b){
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
// 比较函数,用于排序
bool cmp(Point a, Point b){
return a.x != b.x ? a.x < b.x : a.y < b.y;
}
// Andrew算法主函数,返回凸包周长
double Andrew(){
sort(p + 1, p + n + 1, cmp); // 排序
// 求下凸包
for(int i = 1; i <= n; i++){
while(top > 1 && cross(s[top - 1], s[top], p[i]) <= 0) top--;
s[++top] = p[i];
}
int t = top;
// 求上凸包
for(int i = n - 1; i >= 1; i--){
while(top > t && cross(s[top - 1], s[top], p[i]) <= 0) top--;
s[++top] = p[i];
}
double res = 0; // 计算凸包周长
for(int i = 1; i < top; i++) res += dis(s[i], s[i + 1]);
return res;
}
下面回到本题。
解题思路
要判断一个给定顶点按逆时针顺序排列的多边形是否为凹多边形,核心思路是利用凸包相关知识。如果原始多边形是凸多边形,那么它的顶点应该和通过凸包算法得到的凸包顶点数量一致;如果是凹多边形,凸包顶点数量会少于原始多边形顶点数量 。因为凹多边形存在"凹进去"的顶点,这些顶点会被凸包算法排除掉,导致凸包顶点数变少。
AC代码
cpp
pii a[N];
// 按 x 坐标升序排序, y 坐标升序排序
bool cmp(pii x,pii y){
if(x.fi==y.fi) return x.se<y.se;
return x.fi<y.fi;
}
// 计算向量叉积,用于判断三个点的转向关系
int cha(pii a,pii b,pii c){
return (b.fi - a.fi) * (c.se - a.se) - (b.se - a.se) * (c.fi - a.fi);
}
pii tu[N];
int top=0;
void slove(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].fi>>a[i].se;
if(n==3){
cout<<"No";
return ;
}
sort(a+1,a+1+n,cmp);
// 构建下凸包
for(int i=1;i<=n;i++){
// 当栈中至少有两个顶点,且新点导致最后两个顶点和新点构成的线段是"右转"或者共线时,弹出栈顶顶点
while(top>1 && iaji(tu[top-1], tu[top], a[i]) < 0) {
top--;
}
tu[++top] = a[i];
}
// 记录下凸包构建完成后的栈顶位置,用于构建上凸包
int t=top;
// 构建上凸包
for(int i=n-1;i>=1;i--){
while(top>t && cha(tu[top-1], tu[top], a[i]) < 0) {
top--;
}
tu[++top] = a[i];
}
// 如果凸包顶点数量小于原始多边形顶点数量,说明原始多边形有凹进去的点,是凹多边形
if(top < n){
cout<<"Yes";
} else {
cout<<"No";
}
}
F-迷宫穿越
解题思路
用 BFS 找最短路径,同时处理 "消耗卷轴穿过障碍物" 的特殊规则。
需要记录 当前位置 、已用卷轴数量 和 已花费时间 ,确保搜索过程中不重复访问相同状态。
从起点 (1,1) 出发,卷轴使用数 k=0 ,时间 dis=0。遍历上下左右四个方向。 遇到障碍物时,检查卷轴是否足够(k < K),足够则消耗卷轴进入。三维数组标记访问状态 v[tx][ty][tk],避免重复处理。到达终点 (N,M) 时,更新最短时间。
AC代码
cpp
const int N=1006;
const int inf=1e18;
int n,m,K;
string mp[N];
struct node{
int dis,x,y,k;
};
int d[4][4]={{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
void solve()
{
cin>>n>>m>>K;
string s;
for(int i=1;i<=n;i++)
{
cin>>s;
mp[i]="*"+s;
}
bool v[n+1][m+1][15]={false};
int ans=inf;
if(n==1&&m==1)ans=0;
queue<node>q;
q.push({0,1,1,0});
v[1][1][0]=1;
while(q.size())
{
node p=q.front();
q.pop();
int x=p.x,y=p.y,k=p.k,dis=p.dis;
for(int i=0;i<4;i++)
{
int tx=x+d[i][0],ty=y+d[i][1];
if(tx<1||ty<1||tx>n||ty>m)continue;
int tk=(mp[tx][ty]=='#'?k+1:k);
if(v[tx][ty][tk]||tk>K)continue;
q.push({dis+1,tx,ty,tk});
v[tx][ty][tk]=1;
if(tx==n&&ty==m)ans=min(ans,dis+1);
}
}
if(ans==inf)cout<<-1;
else cout<<ans;
}