CSP-j2023题解

T1 小苹果

小 Y 的桌子上放着 n 个苹果从左到右排成一列,编号为从 1 到 n。

小苞是小 Y 的好朋友,每天她都会从中拿走一些苹果。

每天在拿的时候,小苞都是从左侧第 1 个苹果开始、每隔 2 个苹果拿走 1 个苹果。随后小苞会将剩下的苹果按原先的顺序重新排成一列。

小苞想知道,多少天能拿完所有的苹果,而编号为 n 的苹果是在第几天被拿走的?

输入格式

输入的第一行包含一个正整数 n,表示苹果的总数。

输出格式

输出一行包含两个正整数,两个整数之间由一个空格隔开,分别表示小苞拿走所有苹果所需的天数以及拿走编号为 n 的苹果是在第几天。

输入输出样例

输入 #1复制

8

输出 #1复制

5 5

说明/提示

【样例 1 解释】

小苞的桌上一共放了 8 个苹果。
小苞第一天拿走了编号为 1、4、7 的苹果。
小苞第二天拿走了编号为 2、6 的苹果。
小苞第三天拿走了编号为 3 的苹果。
小苞第四天拿走了编号为 5 的苹果。
小苞第五天拿走了编号为 8 的苹果。

【数据范围】

对于所有测试数据有:1≤n≤10^9。

测试点n≤n≤特殊性质
1∼210
3∼510^3
6∼710^6
8∼910^6
1010^9

特殊性质:小苞第一天就取走编号为 n 的苹果。

题解:

我们可以把「每隔 2 个取一个苹果手机」这件事情这样理解:将苹果手机每 3 个分一组,每次取这一组的第一个。

例如有 11 个苹果手机:

标红的是这一轮拿的。如果最后剩下的不足以拼成 3 个一组的,就拼成不完整的一组。

我们可以这样考虑。首先最后一个苹果手机一定是在最后一组的,那么如果想取走这个苹果手机,就相当于这个苹果手机在最后一组的第一个。例如有 11 个和 10 个苹果手机:

可以发现,只有在最后一组仅有 1 个苹果手机时,最后一个苹果手机是这一组的第一个。也就等价于当 n%3=1 时,可以在这一轮取到最后一个苹果手机。

那么我们在求第一问的暴力模拟时,判断当前的 n 是否模 3 为 1。若是,记录下来这是第几轮取苹果手机。这就是第二问的答案。

注意在第一次 n%3=1 时就可以取到最后一个苹果手机了。往后如果还有这样的机会就不算了。

因此这样计算的话时间复杂度为 Θ(log⁡2n)。实际运行时会偏高。

code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
	ll x=0,y=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')y=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*y;
}
int n,a,b; 
int main(){
	n=read();
	while(n>0){
		a++;
		if(b==0&&n%3==1)b=a;
		n-=(n+2)/3;
	}
	printf("%lld %lld",a,b);
	return 0;
}

T2 公路

题目描述

小苞准备开着车沿着公路自驾。

公路上一共有 n 个站点,编号为从 1 到 n。其中站点 i 与站点 i+1 的距离为 vi​ 公里。

公路上每个站点都可以加油,编号为 i 的站点一升油的价格为 ai​ 元,且每个站点只出售整数升的油。

小苞想从站点 1 开车到站点 n,一开始小苞在站点 1 且车的油箱是空的。已知车的油箱足够大,可以装下任意多的油,且每升油可以让车前进 d 公里。问小苞从站点 1 开到站点 n,至少要花多少钱加油?

输入格式

输入的第一行包含两个正整数 n 和 d,分别表示公路上站点的数量和车每升油可以前进的距离。

输入的第二行包含 n−1 个正整数 v1,v2…vn−1​,分别表示站点间的距离。

输入的第三行包含 n 个正整数 a1​,a2​…an​,分别表示在不同站点加油的价格。

输出格式

输出一行,仅包含一个正整数,表示从站点 1 开到站点 n,小苞至少要花多少钱加油。

输入输出样例

输入 #1复制

5 4
10 10 10 10
9 8 9 6 5

输出 #1复制

79

说明/提示

【样例 1 解释】

最优方案下:小苞在站点 1 买了 3 升油,在站点 2 购买了 5 升油,在站点 4 购买了 2 升油。

【数据范围】

对于所有测试数据保证:1≤n≤10^5,1≤d≤10^5,1≤vi≤10^5,1≤ai≤10^5。

测试点n≤n≤特殊性质
1∼58
6∼1010^3
11∼1310^5A
14∼1610^5B
17∼2010^5
  • 特殊性质 A:站点 1 的油价最低。
  • 特殊性质 B:对于所有 1≤i<n,vi​ 为 d 的倍数

题解:

题目大意:

小苞准备开着车沿着公路自驾。

公路上一共有 n 个站点,编号为从 1 到 n。其中站点 i 与站点 i+1 的距离为 vivi​ 公里。

公路上每个站点都可以加油,编号为 i 的站点一升油的价格为 ai​ 元,且每个站点只出售整数升的油。

题目分析:

很明显是一道贪心。

首先,第一次买油一定要买第一个加油站的,而且买的油还要能够走到第二个加油站。

然后,就是第二次买油,如果这里的油价比第一次贵,那就不如第一次多买一点(能够到第三处加油站),否则就在第二处再买油(能走到第三处的量)。

有了这样的思路我们就可以 O(N) 从前往后扫一遍,如果次处加油站比前面最便宜的要贵,那就不如在前面多买一点,否则,就在此处买。

但是这题还有一个小细节就是这次买的油到下次加油站时还有剩余的,再记录一个位置即可。

code:

#include <bits/stdc++.h>
using namespace std;
long long a[100005],v[100005],ans=0,weizhi=0;
long long cnt=0;
int main(){

	long long n,d;
	scanf("%lld%lld",&n,&d);   //输入
	for(int i=1;i<=n-1;i++) scanf("%lld",&v[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	long long minn=a[1];   //第一次一定要买
	for(int i=1;i<=n-1;i++){
		cnt=ceil((v[i]-weizhi)*1.0/d);
		if(i==1){   //特判第一次
			ans+=cnt*minn;
		}
		else{         
			if(a[i]<minn){    //如果这次的便宜
				minn=a[i];
				ans+=ceil((v[i]-weizhi)*1.0/d)*minn;
				cnt=ceil((v[i]-weizhi)*1.0/d);
			}
			else{			///如果这次的贵
				ans+=ceil((v[i]-weizhi)*1.0/d)*minn;
				cnt=ceil((v[i]-weizhi)*1.0/d);
			}
		}
		weizhi=cnt*d-(v[i]-weizhi);     //记录位置
	}
	printf("%lld\n",ans);
	return 0;
}

   T3 一元二次方程

题目描述

现在给定一个一元二次方程的系数 a,b,c,其中 a,b,c 均为整数且 a≠0。你需要判断一元二次方程 ax2+bx+c=0 是否有实数解,并按要求的格式输出。

在本题中输出有理数 v 时须遵循以下规则:

  • 由有理数的定义,存在唯一的两个整数 p 和 q,满足 q>0,gcd(p,q)=1 且 v=qp​。

  • 若 q=1,则输出 {p},否则输出 {p}/{q},其中 {n} 代表整数 n 的值;

  • 例如:

    • 当 v=−0.5 时,p 和 q 的值分别为 −1 和 2,则应输出 -1/2
    • 当 v=0 时,p 和 q 的值分别为 0 和 1,则应输出 0

对于方程的求解,分两种情况讨论:

  1. 若 Δ=b2−4ac<0,则表明方程无实数解,此时你应当输出 NO

  2. 否则 Δ≥0,此时方程有两解(可能相等),记其中较大者为 x,则:

    1. 若 xx 为有理数,则按有理数的格式输出 x。

    2. 否则根据上文公式,x 可以被唯一表示为 x=q1+q2rx=q1​+q2​√r​ 的形式,其中:

      • q1,q2q1​,q2​ 为有理数,且 q2>0q2​>0;
      • rr 为正整数且 r>1,且不存在正整数 d>1 使 d^2∣r(即 rr 不应是 d^2 的倍数);

    此时:

    1. 若 q1≠0,则按有理数的格式输出 q1​,并再输出一个加号 +
    2. 否则跳过这一步输出;

    随后:

    1. 若 q2=1q2​=1,则输出 sqrt({r})
    2. 否则若 q2q2​ 为整数,则输出 {q2}*sqrt({r})
    3. 否则若 q3=1q2q3​=q2​1​ 为整数,则输出 sqrt({r})/{q3}
    4. 否则可以证明存在唯一整数 c,d 满足 c,d>1,gcd(c,d)=1 且 q2=d分之c,此时输出 {c}*sqrt({r})/{d}

    上述表示中 {n} 代表整数 {n} 的值,详见样例。

    如果方程有实数解,则按要求的格式输出两个实数解中的较大者。否则若方程没有实数解,则输出 NO

输入格式

输入的第一行包含两个正整数 T,M,分别表示方程数和系数的绝对值上限。

接下来 T 行,每行包含三个整数 a,b,c。

输出格式

输出 T 行,每行包含一个字符串,表示对应询问的答案,格式如题面所述。

每行输出的字符串中间不应包含任何空格

输入输出样例

输入 #1复制

9 1000
1 -1 0
-1 -1 -1
1 -2 1
1 5 4
4 4 1
1 0 -432
1 -3 1
2 -4 1
1 7 1

输出 #1复制

1
NO
1
-1
-1/2
12*sqrt(3)
3/2+sqrt(5)/2
1+sqrt(2)/2
-7/2+3*sqrt(5)/2

说明/提示

【数据范围】

对于所有数据有:1≤T≤5000,1≤M≤10^3,∣a∣,∣b∣,∣c∣≤M,a≠0。

测试点编号M≤M≤特殊性质 A特殊性质 B特殊性质 C
11
220
310^3
410^3
510^3
610^3
7,810^3
9,1010^3

其中:

  • 特殊性质 A:保证 b=0;
  • 特殊性质 B:保证 c=0;
  • 特殊性质 C:如果方程有解,那么方程的两个解都是整数。

题解:

不难的中模拟,完全按照题意写就可以。

首先读入先判掉 b2<4ac 的情况,输出 NO

然后按照题意逐条模拟即可,注意分数要约分,要写 gcd。其它只有 2 个要注意的点:

  • 分子是 0 不需要输出分母。

  • 输出最大解分子不一定是 −b+b2−4ac​,在2a<0 时为 −b−b2−4ac​。

很坑,考场上因为这个调了好久。

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,M,a,b,c;
int gcd(int x,int y){
	if(x%y==0)return y;
	return gcd(y,x%y);
}
signed main(){
	cin>>T>>M;
	while(T--){
		cin>>a>>b>>c;
		if(b*b-4*a*c<0){
			cout<<"NO\n";continue;
		}
		if((int)sqrt(b*b-4.0*a*c)*(int)sqrt(b*b-4.0*a*c)==b*b-4*a*c){
			int qwq,ttq=2*a,f=1;
			if(ttq>0)qwq=-b+sqrt(b*b-4*a*c);
			else qwq=-b-sqrt(b*b-4*a*c);
			//cout<<qwq<<" "<<ttq<<'\n';
			int g=1;
			if(qwq<0)f=-1,qwq=-qwq;
			if(ttq<0){
				if(f==1)f=-1;
				else f=1;ttq=-ttq;
			}
			if(qwq!=0&&ttq!=0)g=gcd(qwq,ttq);
			qwq/=g;ttq/=g;qwq*=f;
			if(ttq==1||qwq==0)cout<<qwq<<'\n';
			else cout<<qwq<<"/"<<ttq<<'\n';
		}
		else{
			int q1up=-b,q1down=2*a,f=1;
			if(q1up<0)f=-1,q1up=-q1up;
			if(q1down<0){
				if(f==1)f=-1;
				else f=1;q1down=-q1down;
			}
			int g=1;
			if(q1up!=0&&q1down!=0)g=gcd(q1up,q1down);
			q1up/=g;q1down/=g;q1up*=f;
			if(q1up!=0){
				if(q1down==1)cout<<q1up;
				else cout<<q1up<<"/"<<q1down;
				cout<<"+";
			}
			int r=b*b-4*a*c,q2up=1,q2down=2*a;
			for(int i=2;i*i<=r;++i){
				while(r%(i*i)==0){
					r/=(i*i);
					q2up*=i;
				}
			}
			g=1;f=1;
			if(q2down<0)q2down=-q2down;
			if(q2up!=0&&q2down!=0)g=gcd(q2up,q2down);
			q2up/=g;q2down/=g;q2up*=f;
			if(q2up==1&&q2down==1)cout<<"sqrt("<<r<<")\n";
			else if(q2down==1)cout<<q2up<<"*sqrt("<<r<<")\n";
			else if(q2up==1)cout<<"sqrt("<<r<<")/"<<q2down<<'\n';
			else cout<<q2up<<"*sqrt("<<r<<")/"<<q2down<<'\n';
		}
	}
	return 0;
}

T4  旅游巴士

题目描述

小 Z 打算在国庆假期期间搭乘旅游巴士去一处他向往已久的景点旅游。

旅游景点的地图共有 n 处地点,在这些地点之间连有 m 条道路。其中 1 号地点为景区入口,n 号地点为景区出口。我们把一天当中景区开门营业的时间记为 0 时刻,则从 0 时刻起,每间隔 k 单位时间便有一辆旅游巴士到达景区入口,同时有一辆旅游巴士从景区出口驶离景区。

所有道路均只能单向通行。对于每条道路,游客步行通过的用时均为恰好 1 单位时间。

小 Z 希望乘坐旅游巴士到达景区入口,并沿着自己选择的任意路径走到景区出口,再乘坐旅游巴士离开,这意味着他到达和离开景区的时间都必须是 k 的非负整数倍。由于节假日客流众多,小 Z 在旅游巴士离开景区前只想一直沿着景区道路移动,而不想在任何地点(包括景区入口和出口)或者道路上停留

出发前,小 Z 忽然得知:景区采取了限制客流的方法,对于每条道路均设置了一个 “开放时间”ai​,游客只有不早于 ai​ 时刻才能通过这条道路。

请帮助小 Z 设计一个旅游方案,使得他乘坐旅游巴士离开景区的时间尽量地早。

输入格式

输入的第一行包含 3 个正整数 n,m,k,表示旅游景点的地点数、道路数,以及旅游巴士的发车间隔。

输入的接下来 m 行,每行包含 3 个非负整数 ui​,vi​,ai​,表示第 i 条道路从地点 ui​ 出发,到达地点 viv,道路的“开放时间”为 ai​。

输出格式

输出一行,仅包含一个整数,表示小 Z 最早乘坐旅游巴士离开景区的时刻。如果不存在符合要求的旅游方案,输出 -1

输入输出样例

输入 #1复制

5 5 3
1 2 0
2 5 1
1 3 0
3 4 3
4 5 1

输出 #1复制

6

说明/提示

【样例 #1 解释】

小 Z 可以在 3 时刻到达景区入口,沿 1→3→4→5 的顺序走到景区出口,并在 6 时刻离开。

【数据范围】

对于所有测试数据有:2≤n≤10^4,11≤m≤2×10^4,1≤k≤100,1≤ui​,vi​≤n,0≤ai​≤10^6。

测试点编号n≤m≤k≤特殊性质
1∼2101515100ai​=0
3∼5101515100
6∼710^42×10^41ai​=0
8∼1010^42×10^41
11∼1310^42×10^4100ai​=0
14∼1510^42×10^4100ui​≤vi​
16∼2010^42×10^4100

题解:

发现时间 x 之前不能通过不好处理,不妨建反图,从终点往前走,这样限制变成时间 y 之后不能通过,那么到这个点的最短时间就一定是可以通过的。换言之到的越早越好,直接跑最短路,由于边权只有 1 所以考虑广搜即可。

至于倍数限制直接套路地拆点即可,因为要出去最早所以二分出门时间往前广搜看能不能搜到起点即可。

至此两个限制和最早出门时间都被解决,时间复杂度 O(k×nlogV)。

include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+114;
const int top = 1e7+114514;
vector< pair<int,int> > edge[maxn];
int dis[maxn],n,m,k;
inline int pos(int x,int r){return x*k+r;}
bool vis[maxn];
queue<int> q;
inline bool check(int ending){
    memset(dis,0x3f3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[pos(n,0)]=0;
    q.push(pos(n,0));
    while(q.size()>0){
        int u=q.front();
        q.pop();
        if(vis[u]==true) continue;
        vis[u]=true;
        for(pair<int,int> nxt:edge[u]){
            int v=nxt.first;
            if(ending-dis[u]-1>=nxt.second){
                dis[v]=min(dis[v],dis[u]+1);
                q.push(v);
            }
        }
    }
    return dis[pos(1,0)]<=0x3f3f3f;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        int u,v,a;
        cin>>u>>v>>a;
        swap(u,v);
        for(int tm=0;tm<k-1;tm++) edge[pos(u,tm)].push_back(make_pair(pos(v,tm+1),a));
        edge[pos(u,k-1)].push_back(make_pair(pos(v,0),a));
    }
    int l=-1,r=top/k;
    while(l+1<r){
        int mid=(l+r)>>1;
        if(check(mid*k)==true) r=mid;
        else l=mid;
    }
    cout<<(r==top/k?-1:r*k)<<'\n';
    return 0;
}

好了,4到都写完了,爆肝一小时,求求了,给个关注吧= ̄ω ̄=

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值