CSP-J 2023题解

第四题没写完。先补会儿文化课作业,等会再回来继续写。

T1 P9748 [CSP-J 2023] 小苹果

令苹果数量为 \(\texttt{n}\)
容易发现,拿苹果就是每三个一组,取第一个。
需要注意的是,如果以三个一组来考虑拿苹果,最后几个苹果不满三个时也应该算一个组,第一个也要拿走。
形式化的,即当 \(\texttt{n} \bmod 3 \ne 0\) 时,\(\texttt{n} / 3 + 1\) 这个苹果也要被拿走。

第一个小问题通过模拟题意即可解决,判断当前所有苹果是不是刚好可以分成三个三个一组,可以的话就拿走 \(\texttt{n} / 3\) 个,
否则拿走 \(\texttt{n} / 3 + 1\) 个苹果。
形式化的,即为判定 \(\texttt{n} \bmod 3 = 0\) 是否为真。

那么同时,拿走最后一个苹果的判断方式便十分显然了:
关注最后的这一组,如果拿走的这个苹果正是序列最后一个苹果,那么第二个小问题便迎刃而解。
即:每次拿走判断 $\texttt{n} =1 $ 是否成立,成立则最后一组被拿走的便是整个序列的最后一个苹果,即开始时编号为 \(\texttt{n}\) 的苹果。

代码实现:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
 
int main(){
	cin>>n;
	ll ans=0; // ans 表示这队苹果要用多少次才可以取完。
	ll tans=0; // tans 表示最后一个苹果,即编号为 n 的 苹果,是什么时候拿走的。
	while(n){
		if(n%3==1 && tans==0) { tans=ans+1; } // 先判断本轮是否可以拿走第 n 个苹果,如果可以,那么拿走他的应该就是这一次。
     // 需要注意的是,由于本轮还未开始统计,所以要用 ans+1 来维护 tans 。
     // 另外需要注意的是,最后一个取走了会有前一个苹果成为最后一个,所以要特判是否已经被取走来锁定答案。		
			ans++;	
			n-=ceil(1.0*n/3); // 向上取整,规避判断 n%3==0 是否成立。需要注意把 n 换为浮点数避免整型计算直接给小数部分干没了。
	}
		cout<<ans<<" "<<tans<<endl;
	

	return 0;
}

T2 P9749 [CSP-J 2023] 公路

一开始的想法是用单调栈维护每一个加油站之后最便宜的加油站是谁,这样在每个加油站贪心的加好刚刚够的油开到更便宜的加油站即可。
但是我的代码能力太弱了不知道哪里打挂了。
而且这个方法也不够优雅。
另外似乎有类似 \(DP\) 最长不下降子序列思想的解法,这里不做探讨。

于是下面介绍一个一轮过得答案的方法。
(现在写详解仍然觉得那时候我能想到实在是太天才了。)

容易得知,我们贪心的寻找下一个最便宜的加油站,然后在当前加油站加刚刚好够的油,是最优方案。
如果有下一个最便宜的加油站,我们就在当前加油站加够刚刚好够的油,如果没有,说明我们直接加够开到终点的油就完事了。
贪心思想的正确性可以由反证法非常轻松的证明,这里就不写了。

需要注意的是,每升油能够行驶的公里数不一定为 \(1\),所以存在油箱会剩下一点点上次没用完的油。
我们用一个 \(\texttt{now}\) 来维护油箱中剩余油,容易发现如果维护剩余油的升数,会出现非整数的剩余,非常不优雅。
所以我们用 \(\texttt{now}\) 来维护当前油箱剩余的油还能行驶多少公里。

代码实现:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn= 1e5+50;
int n,d;
int v[maxn],a[maxn];

// 快读,不做解释
inline long long read(){
	char readch=getchar(); ll readtmp=0;
	ll readflag=1;
	while(readch<'0' || '9'<readch){if(readch=='-')readflag=-1;readch=getchar();}
	while('0'<=readch && readch<='9'){readtmp=readtmp*10+readch-'0';readch=getchar();}
	return readtmp*readflag;
}

int main(){
	cin>>n>>d;
	for(int i=2; i<=n; i++) v[i]=read(); // 注意题目给的 v 是两站距离差
	for(int i=1; i<=n; i++) a[i]=read();
	
	ll dis=0; // 用 dis 来维护上一个加油站离当前加油站有多远。
	ll ans=0;
	int flag=a[1]; // flag 维护当前最优油价。
	int now=0;
	for(int i=2; i<=n; i++){//我们到达某一站发现有更便宜的油的时候就在之前的flag站加刚好到这一站的油,相当于把题意转化为寻找更便宜加油站的过程。
		dis+=v[i]; // dis 维护上一个加了油的加油站离当前加油站的距离。
		if(i==n){ // 如果已经到了终点啦。
			dis-=now; // 减去当前油箱中剩余的还够开多少公里的油,结果就是在flag站最少应该购买的油。
			ll buy=ceil(1.0*dis/d); 
			now=(buy*d-dis)%d; // 多余的油
			ans+=buy*flag;
			break;
		}
		else if(flag > a[i]){// 遇到了油价更便宜的地方
			dis-=now;
			ll buy=ceil(1.0*dis/d);
			now=(buy*d-dis)%d;
			ans+=buy*flag;
			flag=a[i];					
			dis=0;
		}
	}
	cout<<ans<<endl;

	return 0;
}

T3 P9750 [CSP-J 2023] 一元二次方程

纯纯一道模拟题。
我们用 \(\texttt{specialsolve()}\) 来维护 \(\vartriangle = 0\) 这一类操作。
值得一提的是:

  1. \(\texttt{a} < 0\) 时,可以将 \(\texttt{a,b,c}\) 分别取相反数,对结果没有影响。
  2. \(\texttt{r} = 1\) 时,即 \(\vartriangle\) 是一个完全平方数使得根号可以被消去时,我们可以将其转化为 \(\texttt{specialsolve()}\) 的情况来处理。
    具体而言,就是引入一个 \(\texttt{q2}\)
  3. 题目对 \(\texttt{q2}\) 有一些特殊的讨论要求,事实上是因为对 \(\texttt{q2}\) 与分母 \(2*a\) 消去关系的探讨。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,m;
int sum[2000000],top;
int a,b,c;

inline long long read(){
	char readch=getchar(); ll readtmp=0;
	ll readflag=1;
	while(readch<'0' || '9'<readch){if(readch=='-')readflag=-1;readch=getchar();}
	while('0'<=readch && readch<='9'){readtmp=readtmp*10+readch-'0';readch=getchar();}
	return readtmp*readflag;
}
void specialsolve(int q2,int flag){ // 这个 flag 维护的是后面还有没有东西,如果 flag=1,表明后面没有东西要输出了。
	int tmp= -1*b + q2;
	int tmpagcd= __gcd( abs(tmp), abs(2*a) );
	
	if( tmp < 0) printf("-");
	else if(tmp == 0) { 
		if(flag==1){
			printf("0");
			return ;
		}
	}
	if(tmpagcd == abs(2*a))
		printf("%d", abs(tmp) / tmpagcd);
	else 
		printf("%d/%d", abs(tmp) / tmpagcd, 2*abs(a) / tmpagcd);
}

void solve(int r){
	int q2= 1;
	
	for(int i=top; i>=2; i--){
		if(sum[i] > r) continue;
		if(r==1) break;
		while(r % sum[i] == 0){
			r/= sum[i];
			q2*= sum[i];
		}
	}
	
    q2= sqrt(q2);
	if(r == 1){
		if(a > 0) specialsolve(q2,1); //!
		else specialsolve(-1*q2,1);
		return ;
	}
	
	if(b != 0) specialsolve(0,0), printf("+") ;
	
	a= abs(a);
	
	if(q2 == 2*a){
		//printf("%d*",q2);
		printf("sqrt(%d)",r);
	}
	else if(q2 % (2*a)==0){
		printf("%d*",q2/2/a);
		printf("sqrt(%d)",r);
	}
	else if(q2 == 1){
		printf("sqrt(%d)/",r);
		printf("%d",2*a);
	}
	else {
		int q2agcd=__gcd(q2, 2*a);
		if(q2 != q2agcd)
		printf("%d*sqrt(%d)/%d", q2/ q2agcd, r, 2*a / q2agcd);
		else printf("sqrt(%d)/%d", r, 2*a / q2agcd);
	}		
	
}



int main(){
	cin>>t>>m;
	for(int i=1; i <= 5*m; i++){ // 注意是 i <= 5*m,此处笔者虚空调试四天。代码重构了四次。
		sum[++top]= i*i;
	}
	
	while(t--){
		a= read();
		b= read();
		c= read();
		if(a < 0) {
			a*=-1;
			b*=-1;
			c*=-1;
		}
		
		int deta= b*b - 4*a*c;
		if(deta < 0){ printf("NO"); }
		else if(deta == 0) specialsolve(0,1);
		else solve(deta);
		
		printf("\n");
	}
	

	return 0;
}

T4是一道很典型的图论问题

我们从部分分入手,发现 \(a_i = 0\) 的时候直接分层图上跑 bfs 是不是就有 \(\text{35}\) 分了。
满分部分超时是不是就是因为 bfs 太慢了,此时换用 Dijk 就可以剪枝选边轻松

posted @ 2023-10-30 20:11  [丘李]Chilllee  阅读(259)  评论(1)    收藏  举报