CSP-J 2023答案

博客包含四道算法题,分别是小苹果、公路、一元二次方程和旅游巴士。涵盖题目描述、输入输出格式、样例及数据范围等信息,如小苹果题需计算拿完苹果天数和指定编号苹果被拿天数,公路题求自驾加油最少花费等。

1.小苹果(apple)

题目描述

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

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

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

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

输入格式

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

输出格式

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

输入输出样例

输入 #1复制

8

输出 #1复制

5 5

说明/提示

【样例 11 解释】

小苞的桌上一共放了 88 个苹果。
小苞第一天拿走了编号为 11、44、77 的苹果。
小苞第二天拿走了编号为 22、66 的苹果。
小苞第三天拿走了编号为 33 的苹果。
小苞第四天拿走了编号为 55 的苹果。
小苞第五天拿走了编号为 88 的苹果。

【样例 22】

见选手目录下的 apple/apple2.in 与 apple/apple2.ans。

【数据范围】

对于所有测试数据有:1\leq n\leq 10^91≤n≤109。

测试点n\leqn≤特殊性质
1\sim 21∼21010
3\sim 53∼510^3103
6\sim 76∼710^6106
8\sim 98∼910^6106
101010^9109

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

附件下载

apple.zip418B

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main() {
    int n;
    cin>>n;
    int k=n,d=0,ans=0;
    while(k>0) {
        if(k%3==1&&ans==0) ans=d+1;
        int t=(k-1)/3+1;
        k=k-t;
        d++;
    }
    cout<<d<<" "<<ans;
    return 0;
}

2.公路(road)

题目描述

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

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

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

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

输入格式

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

输入的第二行包含 n - 1n−1 个正整数 v_1, v_2\dots v_{n-1}v1​,v2​…vn−1​,分别表示站点间的距离。

输入的第三行包含 nn 个正整数 a_1, a_2 \dots a_na1​,a2​…an​,分别表示在不同站点加油的价格。

输出格式

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

输入输出样例

输入 #1复制

5 4
10 10 10 10
9 8 9 6 5

输出 #1复制

79

说明/提示

【样例 1 解释】

最优方案下:小苞在站点 11 买了 33 升油,在站点 22 购买了 55 升油,在站点 44 购买了 22 升油。

【样例 2】

见选手目录下的 road/road2.in 与 road/road2.ans。

【数据范围】

对于所有测试数据保证:1 \leq n \leq 10^51≤n≤105,1 \leq d \leq 10^51≤d≤105,1 \leq v_i \leq 10^51≤vi​≤105,1 \leq a_i \leq 10^51≤ai​≤105。

测试点n \leqn≤特殊性质
1\sim 51∼588
6\sim 106∼1010^3103
11\sim 1311∼1310^5105A
14\sim 1614∼1610^5105B
17\sim 2017∼2010^5105
  • 特殊性质 A:站点 11 的油价最低。
  • 特殊性质 B:对于所有 1 \leq i < n1≤i<n,v_ivi​ 为 dd 的倍数。

附件下载

road.zip3.09KB

AC代码:

#include <bits/stdc++.h>
using namespace std;
int n, d, k=0, minn=1e9;
long long cost = 0;
int a[100005] = {0};
int v[100005] = {0};
int main()
{
    cin >> n >> d;
    for (int i=1; i<n; i++) {
        cin >> v[i];
    }
    for (int i=1; i<=n; i++) {
        cin >> a[i];
    }
    for(int i=1;i<n;i++){
        minn=min(minn,a[i]);
        if(k>=v[i]){
            k=k-v[i];
            continue;
        }
        cost += 1ll * ceil(1.0 *(v[i]-k)/d) * minn;
        k = 1ll * ceil(1.0 *(v[i]-k)/d)*d - (v[i]-k);
    }	
    cout << cost<<"\n";
    return 0;
}

3.一元二次方程(uqe)

题目背景

众所周知,对一元二次方程 ax ^ 2 + bx + c = 0, (a \neq 0)ax2+bx+c=0,(a=0),可以用以下方式求实数解:

  • 计算 \Delta = b ^ 2 - 4acΔ=b2−4ac,则:
    1. 若 \Delta < 0Δ<0,则该一元二次方程无实数解。
    2. 否则 \Delta \geq 0Δ≥0,此时该一元二次方程有两个实数解 x _ {1, 2} = \frac{-b \pm \sqrt \Delta}{2a}x1,2​=2a−b±Δ​​。

例如:

  • x ^ 2 + x + 1 = 0x2+x+1=0 无实数解,因为 \Delta = 1 ^ 2 - 4 \times 1 \times 1 = -3 < 0Δ=12−4×1×1=−3<0。
  • x ^ 2 - 2x + 1 = 0x2−2x+1=0 有两相等实数解 x _ {1, 2} = 1x1,2​=1。
  • x ^ 2 - 3x + 2 = 0x2−3x+2=0 有两互异实数解 x _ 1 = 1, x _ 2 = 2x1​=1,x2​=2。

在题面描述中 aa 和 bb 的最大公因数使用 \gcd(a, b)gcd(a,b) 表示。例如 1212 和 1818 的最大公因数是 66,即 \gcd(12, 18) = 6gcd(12,18)=6。

题目描述

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

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

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

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

  • 例如:

    • 当 v = -0.5v=−0.5 时,pp 和 qq 的值分别为 -1−1 和 22,则应输出 -1/2
    • 当 v = 0v=0 时,pp 和 qq 的值分别为 00 和 11,则应输出 0

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

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

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

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

    2. 否则根据上文公式,xx 可以被唯一表示为 x = q _ 1 + q _ 2 \sqrt rx=q1​+q2​r​ 的形式,其中:

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

    此时:

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

    随后:

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

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

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

输入格式

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

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

输出格式

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

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

输入输出样例

输入 #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

说明/提示

【样例 #2】

见附件中的 uqe/uqe2.in 与 uqe/uqe2.ans

【数据范围】

对于所有数据有:1 \leq T \leq 50001≤T≤5000,1 \leq M \leq 10 ^ 31≤M≤103,|a|,|b|,|c| \leq M∣a∣,∣b∣,∣c∣≤M,a \neq 0a=0。

测试点编号M \leqM≤特殊性质 A特殊性质 B特殊性质 C
1111
222020
3310 ^ 3103
4410 ^ 3103
5510 ^ 3103
6610 ^ 3103
7, 87,810 ^ 3103
9, 109,1010 ^ 3103

其中:

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

附件下载

uqe.zip29.51KB

AC代码:

#include<bits/stdc++.h>
using namespace std;
int t,m,a,b,c;
int gcd(int p,int q){
    int u;
    while(q!=0){
        u=p%q;
        p=q;
        q=u;
    }
    return p;
}
void work1(int p,int q){
    if(p*q<0) cout<<'-';
    if(p<0) p=0-p;
    if(q<0) q=0-q;
    int g=gcd(p,q);
    if(g==q) cout<<p/g;	
    else cout<<p/g<<'/'<<q/g;
    return;
}
void work2(int p,int q){
    if(p<0) p=-p;
    if(q<0) q=-q;
    int u=1;
    for(int i=sqrt(p); i>=2; i--) {
        if(p%(i*i)==0){
            p=p/(i*i);
            u=u*i;
            break;
        }
    }
    int g=gcd(u,q);
    u=u/g;
    q=q/g;
    if(u!=1) cout<<u<<"*";
    if(p!=1) cout<<"sqrt("<<p<<")";
    if(q!=1) cout<<"/"<<q;
}
int main(){
//    freopen("uqe2.in","r",stdin);
//    freopen("uqe2.ans","w",stdout);
    cin>>t>>m;
    while(t--) {
        cin>>a>>b>>c;
        int d=b*b-4*a*c;
        if(d<0){
            cout<<"NO";
        }else{ 
            int q=sqrt(d);
            if(q*q==d) { 
                double ans1=1.0*(-b+q)/(2*a);
                double ans2=1.0*(-b-q)/(2*a);
                if(ans1>ans2) {
                    if(-b+q==0) cout<<0;
                    else work1(-b+q,2*a);
                } else {
                    if(-b-q==0) cout<<0;
                    else work1(-b-q,2*a);
                }
            } else { 
                if(b!=0) work1(-b,2*a);
                if(b!=0) cout<<"+";
                work2(d,2*a);
            }

        }
        cout<<"\n";
    }
    return 0;
}

4.旅游巴士(bus)

题目描述

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

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

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

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

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

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

输入格式

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

输入的接下来 mm 行,每行包含 3 个非负整数 u _ i, v _ i, a_ iui​,vi​,ai​,表示第 ii 条道路从地点 u _ iui​ 出发,到达地点 v _ ivi​,道路的“开放时间”为 a _ iai​。

输出格式

输出一行,仅包含一个整数,表示小 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 可以在 33 时刻到达景区入口,沿 1 \to 3 \to 4 \to 51→3→4→5 的顺序走到景区出口,并在 66 时刻离开。

【样例 #2】

见附件中的 bus/bus2.in 与 bus/bus2.ans

【数据范围】

对于所有测试数据有:2 \leq n \leq 10 ^ 42≤n≤104,1 \leq m \leq 2 \times 10 ^ 41≤m≤2×104,1 \leq k \leq 1001≤k≤100,1 \leq u _ i, v _ i \leq n1≤ui​,vi​≤n,0 \leq a _ i \leq 10 ^ 60≤ai​≤106。

测试点编号n \leqn≤m \leqm≤k \leqk≤特殊性质
1 \sim 21∼210101515100100a _ i = 0ai​=0
3 \sim 53∼510101515100100
6 \sim 76∼710 ^ 41042 \times 10 ^ 42×10411a _ i = 0ai​=0
8 \sim 108∼1010 ^ 41042 \times 10 ^ 42×10411
11 \sim 1311∼1310 ^ 41042 \times 10 ^ 42×104100100a _ i = 0ai​=0
14 \sim 1514∼1510 ^ 41042 \times 10 ^ 42×104100100u _ i \leq v _ iui​≤vi​
16 \sim 2016∼2010 ^ 41042 \times 10 ^ 42×104100100

附件下载

bus.zip155.15KB

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=20010,inf=0x3f3f3f3f;
int n,m,k;
int h[N],e[M],ne[M],w[M],idx=0;
int dis[N][105],path[N];
void add(int a,int b,int c) {
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
struct node {
    int v,d;
} t;
struct cmp{
    bool operator()(const node a,const node b) {
        return a.d>b.d;
    }
};
int dijkstra() {
    memset(dis,0x3f,sizeof dis);
    priority_queue<node,vector<node>,cmp> heap;
    path[1]=0;
    heap.push({1,0});
    while(!heap.empty()) {
        t=heap.top();
        heap.pop();
        if(dis[t.v][t.d%k]<t.d) continue;
        for(int i=h[t.v]; i!=-1; i=ne[i]) {
            int j=e[i];
            int tt=t.d;
            if(tt+1<w[i]) {
                tt+=((w[i]-1-tt+k)/k*k);
            }
            if(tt+1<dis[j][(tt+1)%k]) {
                dis[j][(tt+1)%k]=tt+1;
                heap.push({j,dis[j][(tt+1)%k]});
            }
        }
    }
    if(dis[n][0]>=inf) return -1;
    else return dis[n][0];
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
    memset(h,-1,sizeof h);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        int u,v,a;
        cin>>u>>v>>a;
        add(u,v,a);
    }
    cout<<dijkstra();
    return 0;
}

制作不易,希望点个关注~~~

<think>我们注意到用户的问题是关于洛谷P9748 [CSP-J 2023] 小苹果的题目解析或解法。 根据引用[2]和引用[4],我们可以得到题目的描述和部分代码。 题目描述: 小Y的桌子上放着n个苹果从左到右排成一列,编号为从1到n。 小苞每天从左侧第1个苹果开始,每隔2个苹果拿走1个苹果。随后将剩下的苹果按原先的顺序重新排成一列。 问题:多少天能拿完所有的苹果?编号为n的苹果是在第几天被拿走的? 输入格式:一个正整数n(1<=n<=10^9)。 输出格式:两个正整数,分别表示拿完所有苹果所需的天数和编号为n的苹果被拿走的天数。 样例输入:8 样例输出:5 5 解题思路: 观察每天拿苹果的规律:第一天拿走编号为1,4,7,...(即所有模3余1的苹果);第二天重新排列后,原来的2号苹果变成1号,5号变成2号,8号变成3号,然后同样拿走所有模3余1的苹果(即原序列的2,6);第三天同理。 我们发现,每天拿走的苹果数量大约是当前苹果总数的三分之一(具体为ceil(n/3)?)。但更重要的是,我们需要模拟这个过程直到苹果被拿完,同时记录第n号苹果被拿走的时间。 然而,n最大为10^9,不能直接模拟每一天。我们需要寻找数学规律。 设第k天开始时苹果数量为n_k(n0=n),那么第k天拿走的苹果数量为:t_k = ceil(n_k / 3) ?实际上,从样例看,第一天8个苹果,拿走的是第1,4,7号,共3个(即(8-1)/3+1=3)。更一般地,第k天拿走的苹果数量为 t_k = (n_k + 2) / 3 ?或者 (n_k-1)//3 + 1。 实际上,根据规则,每次拿走的是第1个,然后每隔2个拿1个,所以相当于每3个苹果拿走1个。那么: - 如果n_k mod 3 == 0,则拿走的数量为 n_k/3(因为每3个拿1个,正好拿完三组中的第一个) - 否则,拿走的数量为 n_k//3 + 1(因为最后不足3个,也会拿走第一个) 但也可以统一为:t_k = (n_k + 2) // 3 (因为整数除法是向下取整,而我们要的是向上取整的另一种表达?) 实际上,我们也可以这样计算:t_k = (n_k - 1) // 3 + 1 (因为第一个苹果必拿,然后每3个拿1个,所以相当于前面每3个一组,组数就是拿的数量) 验证: 当n_k=8: (8-1)//3+1 = 7//3+1 = 2+1=3,正确。 当n_k=5: (5-1)//3+1=4//3+1=1+1=2,但实际第二天开始时5个苹果,拿走的是重新排列后的第1,4号(即原来的2,5号),所以拿2个,正确。 当n_k=3: (3-1)//3+1=2//3+1=0+1=1,正确(拿走第一个)。 当n_k=2: (2-1)//3+1=1//3+1=0+1=1,正确(拿走第一个,剩下一个)。 当n_k=1: (1-1)//3+1=0+1=1,正确。 所以,第k天拿走的苹果数量为:t_k = (n_k - 1) // 3 + 1。 那么,第k天结束后剩下的苹果数量为:n_{k+1} = n_k - t_k。 我们还需要知道第n号苹果是在第几天被拿走的。注意,第n号苹果在序列中的位置会随着每天重新排列而变化。但是,我们观察到: - 当某一天,剩下的苹果中,最后一个苹果(即原序列的第n号)在重新排列后处于第一个位置时,它就会被拿走。因为每天都是从第一个开始拿,且第一个必拿(因为第一个位置是1,1 mod 3 =1,会被拿走)。 那么,什么时候最后一个苹果会处于第一个位置?实际上,在重新排列后,苹果的位置是连续的,所以最后一个苹果的位置就是当前苹果的数量。那么,当当前苹果的数量满足:位置n_k(即最后一个苹果的位置)模3等于1时,它就会被拿走。 但是,我们如何知道第n号苹果在每一天的位置?实际上,我们不需要跟踪每个苹果,只需要关注第n号苹果。注意,第n号苹果在每一天的序列中,它的位置就是当前序列的最后一个位置(因为重新排列后,苹果的编号顺序不变,只是去掉了被拿走的,然后重新按顺序排,所以原来的最后一个苹果只要没被拿走,就会在新的序列的最后一个位置)。 因此,第n号苹果在每一天开始时的位置就是当前苹果的总数(即它在最后)。那么,它被拿走当且仅当:当前苹果总数 mod 3 == 1?因为当前苹果总数就是它的位置,而如果位置 mod 3 == 1,那么它就会被拿走(因为每天拿的是位置1,4,7,...,即所有位置模3余1的苹果)。 但是,我们验证一下样例:n=8。 第一天:苹果总数8,8 mod 3 = 2,所以不会被拿走(第一天拿的是1,4,7,而8是最后一个,位置8 mod3=2,所以没被拿走)。 第二天:剩下的苹果是2,3,5,6,8(5个),位置5 mod3=2,不会被拿走(第二天拿的是重新排列后的位置1,4,即原来的2,6)。 第三天:剩下的苹果是3,5,8(3个),位置3 mod3=0,不会被拿走?但是第三天拿的是重新排列后的位置1(即原来的3号),所以8号还在,位置是3,3 mod3=0,而规则是拿位置1(1 mod3=1),所以3号被拿走,8号没被拿。 第四天:剩下5,8(2个),位置2 mod3=2,不会被拿走(第四天拿的是重新排列后的位置1,即5号),8号还在。 第五天:只剩下8号,位置1 mod3=1,被拿走。 所以,我们观察发现,当第k天,剩下的苹果中最后一个苹果(即n号)的位置(也就是当前苹果数量n_k)满足:n_k mod 3 == 1?不对,因为第五天1 mod3=1,被拿走;第一天8 mod3=2,没拿;第二天5 mod3=2,没拿;第三天3 mod3=0,没拿;第四天2 mod3=2,没拿。 那么,什么情况下最后一个苹果会被拿走?实际上,当某一天,当前苹果数量n_k满足:n_k % 3 == 1?不对,因为第三天3%3=0,但最后一个苹果(8号)没有被拿走(第三天只拿走了3号)。那么,我们换一个角度:在每一天,我们只拿位置模3余1的苹果,而最后一个苹果的位置是n_k,所以当n_k % 3 == 1时,那么最后一个苹果的位置就满足被拿走的条件。 但是,我们再看第二天:5%3=2,但第二天并没有拿最后一个(因为拿的位置1和4,4小于5,所以最后一个没被拿)。第五天:1%3=1,被拿。所以条件就是:当某一天开始时的苹果总数n_k满足n_k mod 3 == 1时,那么最后一个苹果(即n号)会在这一天被拿走。 注意:这个条件在第一天开始时,如果n mod3==1,那么第一天就会拿走最后一个苹果(如果n=1,那么第一天就拿走;如果n=4,那么第一天拿走1,4,所以4号在第一天被拿走)。 因此,我们只需要在模拟过程中,检查当前剩下的苹果数n_k: 1. 如果n_k % 3 == 1,那么说明这一天最后一个苹果会被拿走,而且这是这个苹果第一次满足条件(因为一旦被拿走,后面就没有了),所以我们记录这一天(即当前天数)为第n号苹果被拿走的天数。 2. 但是,注意:有可能在之前的某一天已经满足条件了?实际上,每个苹果只会被拿走一次,所以一旦满足条件,我们记录后,以后就不需要再记录。 但是,我们如何知道在之前是否已经记录过?因为最后一个苹果只会在第一次满足条件的那一天被拿走。所以我们可以用一个标记,一旦记录过,就不再记录。 算法步骤: 初始化: 剩余苹果数量:n 天数:day = 0 拿完所有苹果的天数:ans1 第n号苹果被拿走的天数:ans2 = 0(初始为0,表示还没被拿走) 循环直到n为0: day++ // 新的一天 // 检查当前n是否满足:n % 3 == 1 且 还没有记录过ans2 if (n % 3 == 1 && ans2 == 0) { ans2 = day; } // 计算这一天拿走的苹果数量:t = (n-1)//3 + 1 t = (n-1)/3 + 1 // 注意:整数除法,向下取整 // 更新剩余的苹果数量 n = n - t 循环结束后: ans1 = day 输出:ans1 和 ans2 但是,这里有一个问题:当n=1时,第一天:n=1,满足1%3==1,所以ans2=1;然后t=(1-1)//3+1=0+1=1,n=1-1=0,结束。所以输出1 1,正确。 当n=2时: 第一天:n=2,2%3=2,不记录ans2;t=(2-1)//3+1=1//3+1=0+1=1,剩下1。 第二天:n=1,满足1%3==1,ans2=2;t=(1-1)//3+1=0+1=1,剩下0。 所以输出2 2,正确。 当n=3时: 第一天:n=3,3%3=0,不记录;t=(3-1)//3+1=2//3+1=0+1=1,剩下2。 第二天:n=2,2%3=2,不记录;t=(2-1)//3+1=1//3+1=0+1=1,剩下1。 第三天:n=1,记录ans2=3;t=1,结束。 输出3 3,但根据题目描述,n=3时: 第一天:拿1,4,...(但只有3个,所以拿1号(位置1)和4号不存在,所以只拿1号?不对,规则是每隔2个拿1个,所以位置1,4(4>3,所以只拿1号),所以第一天拿1个,剩下2,3。 第二天:重新排列,2号变成1号,3号变成2号。然后从1号开始,每隔2个拿1个:拿1号(2号)和4号(不存在),所以拿1个(2号),剩下3号。 第三天:拿3号(位置1),所以3号在第三天被拿走。所以输出3 3,正确。 但是,样例n=8,输出5 5。 按照我们的算法: 第一天:n=8, 8%3=2 -> 不记录;t=(8-1)//3+1=7//3+1=2+1=3,剩下5。 第二天:n=5, 5%3=2 -> 不记录;t=(5-1)//3+1=4//3+1=1+1=2,剩下3。 第三天:n=3, 3%3=0 -> 不记录;t=(3-1)//3+1=2//3+1=0+1=1,剩下2。 第四天:n=2, 2%3=2 -> 不记录;t=(2-1)//3+1=1//3+1=0+1=1,剩下1。 第五天:n=1, 1%3=1 -> 记录ans2=5;t=1,剩下0。 所以ans1=5,ans2=5,正确。 但是,为什么在第三天时,n=3,3%3=0,而第三天并没有拿走8号?因为8号在第三天时并不是最后一个?不对,第三天开始时,剩下的苹果是3,5,8(重新排列后为3,5,8,其中3号在位置1,5在位置2,8在位置3)。所以8号的位置是3,3 mod3=0,所以不满足条件(条件要求位置模3余1)。所以正确。 因此,我们只需要按照上述模拟过程即可。但是n最大10^9,而模拟的次数大约是log_{3/2}(n)(因为每次减少大约1/3,所以总天数大约是log_{3/2}(n)),这个对数级别是很快的(10^9大约几十次)。 代码实现(C++): ```cpp #include <iostream> using namespace std; int main() { long long n; cin >> n; int days = 0; // 总天数 int pick_n_day = 0; // 记录第n号苹果被拿走的天数 while (n) { days++; // 如果在这一天,最后一个苹果(即n号)会被拿走(即当前苹果数模3等于1)并且还没有被拿走 if (pick_n_day == 0 && n % 3 == 1) { pick_n_day = days; } // 计算这一天拿走的苹果数量 long long t = (n - 1) / 3 + 1; n -= t; } cout << days << " " << pick_n_day << endl; return 0; } ``` 注意:n是10^9,所以用long long。而t的计算:因为n最大10^9,所以(n-1)/3是整数除法(向下取整),然后加1,所以t大约是n/3,不会溢出。 但是,我们考虑n=0的情况,循环条件就退出了。所以没问题。 测试:n=1,2,3,8都正确。 引用[4]中的代码也是类似的: ```cpp #include<iostream> using namespace std; int n,flag,pick_end; // 这里pick_end就是记录第n号苹果被拿走的天数 long long ans; // 总天数 int main() { cin>>n; while(n)//当还有苹果,就继续拿: { //每次拿t个 ans++; int t=1+(n-1)/3; if((n-1)%3==0&&pick_end==0){ // 注意:这里条件写的是(n-1)%3==0 pick_end=ans; } n-=t; } cout<<ans<<' '<<pick_end; return 0; } ``` 这里注意,条件写的是`(n-1)%3==0`,这和我们写的`n%3==1`是等价的,因为: (n-1)%3==0 => n-1=3k => n=3k+1 => n%3=1。 所以两种写法都可以。 因此,我们提供了两种条件判断方式。 总结: 本题是一个模拟题,但由于n很大,不能逐天模拟,而需要利用数学规律(每次拿走的苹果数量约为1/3)来快速计算,这样总天数是O(log n)级别,可以接受。 引用: [^2] 提供了题目描述 [^4] 提供了代码实现</think>### P9748 [CSP-J 2023] 小苹果 题目解析 #### 问题描述 给定 $n$ 个苹果($1 \leq n \leq 10^9$),每天从左侧第 1 个苹果开始,**每隔 2 个苹果拿走 1 个**(即拿走所有位置满足 $pos \equiv 1 \pmod{3}$ 的苹果)。剩余苹果重新排列后重复过程。求: 1. 拿完所有苹果所需的天数 2. 编号为 $n$ 的苹果被拿走的天数 #### 核心算法 使用**数学模拟法**,时间复杂度 $O(\log n)$(对数级别,可处理 $n=10^9$): ```cpp #include <iostream> using namespace std; int main() { long long n; cin >> n; int days = 0, pick_n_day = 0; // 总天数,n号苹果被拿天数 while (n > 0) { days++; // 检查当前最后一个苹果(编号n)是否被拿走 if (pick_n_day == 0 && (n % 3 == 1)) { pick_n_day = days; } // 计算当天拿走的苹果数量 long long t = (n - 1) / 3 + 1; // 关键公式 n -= t; // 更新剩余苹果 } cout << days << " " << pick_n_day; return 0; } ``` #### 算法原理 1. **每天拿走的苹果数 $t$**: $$t = \left\lfloor \frac{n-1}{3} \right\rfloor + 1$$ - 例如 $n=8$:$t = \lfloor (8-1)/3 \rfloor + 1 = \lfloor 7/3 \rfloor + 1 = 3$ - 推导:每 3 个苹果拿走 1 个,剩余 $n$ 苹果可分成 $\lfloor (n-1)/3 \rfloor$ 完整组 + 1 个剩余苹果 2. **编号 $n$ 的苹果被拿条件**: - 当剩余苹果数 $n$ 满足 $n \equiv 1 \pmod{3}$ 时,**最后一个苹果(编号 $n$)的位置满足 $pos \equiv 1 \pmod{3}$**,当天被拿走 - 只需记录第一次满足条件的天数(因苹果只被拿一次) 3. **更新剩余苹果**: $$n_{\text{new}} = n - t$$ 迭代直至 $n=0$ #### 样例验证($n=8$) | 天数 | 当前 $n$ | $t$ 计算 | 拿走苹果 | 剩余苹果 | $n \equiv 1 \pmod{3}$? | 记录 $n$ 被拿 | |------|----------|------------------|--------------|----------|------------------------|---------------| | 1 | 8 | $(8-1)/3+1=3$ | 1, 4, 7 | 5 | $8\%3=2$ → 否 | - | | 2 | 5 | $(5-1)/3+1=2$ | 2, 6 | 3 | $5\%3=2$ → 否 | - | | 3 | 3 | $(3-1)/3+1=1$ | 3 | 2 | $3\%3=0$ → 否 | - | | 4 | 2 | $(2-1)/3+1=1$ | 5 | 1 | $2\%3=2$ → 否 | - | | 5 | 1 | $(1-1)/3+1=1$ | 8 | 0 | $1\%3=1$ → 是 | 第 5 天 | 输出:`5 5`,符合样例[^2][^3] #### 关键公式推导 - **拿取数量 $t$**: 设当前苹果数 $n$,每 3 个一组拿走第一个: $$t = \begin{cases} \dfrac{n}{3} & \text{if } n \equiv 0 \pmod{3} \\ \left\lfloor \dfrac{n}{3} \right\rfloor + 1 & \text{otherwise} \end{cases}$$ 统一为 $t = \left\lfloor \dfrac{n-1}{3} \right\rfloor + 1$(利用整数除法向下取整) - **位置条件**: 编号 $n$ 的苹果始终位于末尾,其位置 $pos = n$。当 $n \equiv 1 \pmod{3}$ 时,$pos$ 满足拿取条件。 #### 注意事项 - **数据范围**:$n$ 最大 $10^9$,需用 `long long` - **边界**:$n=1$ 时,第 1 天即完成 - **等价判断**:`(n % 3 == 1)` 等价于 `((n-1) % 3 == 0)`(代码可互换)[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值