2025“钉耙编程”中国大学生算法设计春季联赛(2)

作者很菜,看不懂补药( ̄(工) ̄)俺~~~~

学历史导致的

这个就直接暴力就行了,签到题~

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define vi vector<int>
#define sz(x) (int)x.size()
map<string ,int>ans;
string tian[]={ "jia", "yi", "bing","ding", "wu", "ji", "geng", "xin", "ren", "gui"};
string di[]={"zi","chou","yin","mao", "chen","si","wu", "wei", "shen", "you","xu", "hai"};

void solve(){
    string c;
    cin>>c;
    cout<<ans[c]<<endl;

}

signed main()
{
    int ii=0,jj=0;
    for(int i=1984;i<=2043;i++){
        string c=tian[ii]+di[jj];
        ii=(ii+1)%10;
        jj=(jj+1)%12;
        ans[c]=i;
    }
    int t;cin>>t;
    while(t--)
        solve();
    return 0;
}

学数数导致的

这个题目是要求找出数列 a1…an-1​,问有多少个正整数数对 (p,q)使得数列中存在子序列 p,0,p,q 

首先应该读懂题目,这是个正整数对,所以我们统计的时候0不能算在内,其次这是个序列

那么我们就可以先倒序遍历,统计当前位置后面出现的数字的种类个数

然后从头往后遍历,遇到0就更改idx的值,否则就进行另一个判断,看看是不是第一次出现,如果是的话,就给first_idx存储下标,这个的作用是为了防止0 1 1 这样的序列也计算进去了,其他的都挺简单啦

具体的看代码吧

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define vi vector<int>
#define sz(x) (int)x.size()
//模拟
//记录之前的值即可
void solve(){ 
    int n;cin>>n;
    vi f1(1e6+10,0);
    vi cnt(1e6+10,0);
    vi f2(1e6+10,0);
    vi a(1e6+10,0);
    vi count(1e6+10,0);
    vi first_idx(1e6+10,1e6+10);
    int c=0;
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=n-1;i>=0;i--){
        if(f1[a[i]]==0&&a[i]!=0){
            f1[a[i]]=1;
            c++;
        }
        count[i]=c;
    }
    int idx=100001001;
    int ans=0;
    for(int i=0;i<n;i++){
        if(a[i]!=0){
            if(first_idx[a[i]]==1e6+10)first_idx[a[i]]=i;
            if(cnt[a[i]]==0||f2[a[i]]==1){//没出现过或者已经算过了
                cnt[a[i]]++;
            }else if(first_idx[a[i]]<idx){//之前已经出现过了并且最先出现的小于idx
                if(idx<i){//可以计算了
                    ans+=count[i+1];
                    f2[a[i]]=1;
                }
            }
        }else{
            idx=i;
        }
    }
    cout<<ans<<endl;
}

signed main()
{
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int t;cin>>t;
    while(t--)
    solve();
    return 0;
}

学 DP 导致的

要求找最长子序列,直接打板子就好啦~

但是k需要处理一下,最长就是拼接26次

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define vi vector<int>
#define sz(x) (int)x.size()
//最长子序列模板啦

void solve(){   
    
    int  q[100010]={0};
    string a;cin>>a;;
    string k;cin>>k;
    int t = (k.length() >= 3) ? 26 : min(26, stoi(k));
    string b=a;
    for(int i=0;i<t-1;i++)a=a+b;
    int n=sz(a);
    q[0] = -2e9;
    int len = 0;
    for (int i = 0; i < n; i ++)
    {
        int l = 0, r = len;
        while (l < r)
        {
            int mid = (l + r + 1) >> 1;
            if (q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }
    cout<<len<<endl;
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;cin>>t;
    while(t--)
    solve();
    return 0;
}

学几何导致的

这个是个规律,我们每次实现必须O(1)来实现

观察可以发现,如果k为奇数,那么一定没有结果啦

其次我们对于0来说,第一个垂直的是0+k/2 ,第二个是0+k/2+k,第三个是0+k/2+k+k.....

那么到这里应该就发现规律了,一直加到n即可,那么第二个就是从1开始了,第一个垂直的是1+k/2 ,第二个是1+k/2+k,第三个是1+k/2+k+k.....

在这我们先使k/=2;

我们可以得到0开始的垂直的数量为t=(n-1-k)/(k*2)+1;

那么是不是最后还会剩几个,是多少呢?其实余数就是mod=(n-1-k)%(k*2);

那么数量为t的计算完了,依次是t-1,t-2......1 ,而对于每个,他的数量都是2*k

直接求和相乘就行了ans=t*(mod+1)+((1+t-1)*(t-1)/2)*(2*k);

看代码吧

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define vi vector<int>
#define sz(x) (int)x.size()

void solve(){   
    int n,k;cin>>n>>k;
    if(k%2==1||(2*n<=k)){//差个条件判断
        cout<<0<<'\n';
        return;   
    }
    k/=2;
    int ans=0;
    int t=(n-1-k)/(2*k)+1;
    int mod=(n-1-k)%(2*k);
    ans=t*(mod+1)+((1+t-1)*(t-1)/2)*(2*k);
    cout<<ans<<'\n';

}

signed main()
{
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int t;cin>>t;
    while(t--)
    solve();
    return 0;
}

<think>嗯,用户想了解2025钉耙编程中国大学生算法设计春季联赛第一场的航线试题解析。首先,我需要回忆一下这个题目的具体内容。根据用户提供的引用[1],题目是一个最短路径问题,结合网格结构和转向的额外费用。应该类似于网格中的移动,每次转向可能需要额外成本,所以传统的BFS可能不够,需要用到Dijkstra算法或者带有状态记录的BFS。 用户的问题是关于这道题的解析,我需要先确定题目的具体要求。比如,网格的大小、移动的方向(四个方向还是八个方向)、转向费用的具体数值,以及起点和终点的位置。不过根据引用中的描述,可能题目中的转向费用是每次转向增加一个固定成本,比如1单位。所以,路径的总成本不仅包括移动步数,还包括转向次数。 接下来,我需要考虑如何建模这个问题。每个节点的状态不仅要记录坐标,还要记录当前移动的方向,这样才能判断下一步是否转向。例如,在Dijkstra算法中,优先队列中的每个元素应该包含坐标、方向和当前的总成本。这样,每次移动时,如果方向改变,成本就加1,否则成本不变。 然后,如何处理状态转移?比如,从当前坐标向四个方向移动,如果下一步的方向与当前方向不同,则总成本+1(转向费)+1(移动费),或者可能题目中的移动本身每次移动有固定成本,转向额外加。需要明确题目的具体条件。假设每次移动基础成本是1,转向额外加k,比如k=1。这时候,状态转移时需要比较不同方向到达同一节点的总成本,选择最小的。 另外,需要注意重复状态的判断。比如,到达同一个节点,但方向不同,可能会有不同的成本。因此,visited数组或者距离数组需要记录每个节点在不同方向下的最小成本。例如,使用三维数组dist[x][y][dir],其中dir表示方向,这样在更新时比较不同方向下的成本。 可能的测试案例是什么?比如,一个网格中有障碍物,或者某些路径需要绕行,转向次数多但路径短,或者直行次数多但路径长。需要分析哪种情况下哪种策略更优。 然后,具体的算法步骤应该是怎样的?首先初始化起点的四个方向(如果是四个方向移动的话),或者如果是起点没有方向,第一次移动可以选择任意方向,不需要转向费用。这时候初始状态可能需要特殊处理。或者题目中起点没有初始方向,所以第一次移动的四个方向都不需要转向费?或者可能第一次移动需要算作转向?这需要根据题目描述来定,但用户没有给出详细题目,只能根据常规情况推断。 假设起点可以任意选择初始方向,那么初始状态的各个方向成本可能为0或者初始移动成本。比如,从起点出发,向四个方向移动,每个方向的第一次移动可能不需要转向费,或者需要?如果题目中的转向费用是每次改变方向时产生,那么初始没有方向,第一次移动选择任何方向都不需要转向费。之后每次移动如果方向改变,则加转向费。 因此,在算法实现中,每个节点的状态包括坐标和当前方向。初始时,可以将四个方向的可能都加入优先队列,成本为移动成本(比如1)加上转向费(初始方向为无,所以第一次移动无转向费?或者初始方向视为某个特殊值,比如-1,当方向改变时判断是否需要费用。比如,如果当前方向是-1,那么无论下一步哪个方向都不需要转向费。例如,在代码中,初始状态的方向为-1,然后移动时,如果当前方向是-1,那么新的方向无论是什么,转向费为0。否则,如果新的方向与当前方向不同,则转向费加k。 所以,算法的大致步骤是: 1. 初始化优先队列,将起点四个方向的可能移动加入队列,成本为1(移动费)+0(转向费,因为是初始方向)。或者,如果起点不需要移动,则可能需要不同的处理。这可能需要根据题目具体要求调整。 2. 使用Dijkstra算法,每次取出当前成本最小的状态(坐标,方向),然后遍历四个可能的移动方向。对于每个新方向,计算转向费用:如果当前方向与新方向不同,则加k,否则不加。总成本是当前总成本 + 移动费用(1) + 转向费用(如果有的话)。 3. 更新距离数组dist[x][y][new_dir],如果新的总成本比之前记录的小,则更新,并将新状态加入队列。 4. 当到达终点时,返回最小的总成本。 此外,需要考虑网格中的障碍物,某些坐标可能不可达。例如,在移动时需要检查目标坐标是否合法且不是障碍物。 现在,用户提供的引用[1]中提到的可能是用BFS或Dijkstra。由于存在不同的权重(转向费用可能和移动费用相加),如果转向费用是相同的,比如每次转向加1,那么总成本是移动次数加上转向次数。这种情况下,Dijkstra算法更合适,因为不同路径的总成本可能不同。而BFS只有在所有移动成本相同的情况下才有效,否则需要使用优先队列来处理。 例如,每个移动的成本可能为1,转向费用为k。总成本是移动步数 + k*转向次数。这时候,如果k是整数,可能可以用BFS的变形,但如果k可能为不同值,比如k=2,则需要Dijkstra。 因此,正确的算法选择是Dijkstra,因为每个移动和转向的总成本可能不是均匀的。 在代码实现时,可以用优先队列,每个节点保存坐标、方向、总成本。距离数组记录每个位置在各个方向下的最小成本。当新的路径到达同一位置和方向时,只有当总成本更小时才更新。 例如,Python中的实现可能如下: 使用一个优先队列(heapq),每个元素是(总成本, x, y, dir)。其中dir表示当前方向,比如0-3对应上下左右或者四个方向。 初始化时,起点的四个方向都可能被加入队列,但需要考虑初始是否有方向。比如,如果起点没有初始方向,则第一次移动的四个方向都不需要转向费用,总成本是移动成本(1)。或者,如果起点本身不算移动,则第一次移动时转向费用为0? 这个问题需要更明确的题目描述,但根据常规类似问题,假设初始时方向为None,第一次移动的任何方向都不需要转向费。因此,初始状态将四个方向的可能移动加入队列,成本为1(移动费) +0(转向费)=1。例如,起点是(sx, sy),那么对于每个可能的四个方向,检查移动后的坐标是否有效,然后将这些状态加入队列,成本各为1。 然后,每次处理队列中的元素,尝试四个方向。例如,当前状态是(x,y, dir), 总成本为cost。对于每个新的方向new_dir,如果new_dir != dir,则新的总成本是 cost +1(移动费) +k(转向费);如果new_dir == dir,则新的总成本是 cost +1。然后比较新的坐标的new_dir方向下的dist值,如果更小,则更新。 这样就能处理转向费用的问题。 现在,针对用户的问题,如何给出解析?需要分步骤说明解题思路、算法选择、状态设计、代码实现(如果可能的话)以及复杂度分析。 可能的代码框架: 使用Dijkstra算法,每个状态包含坐标和方向,优先队列按总成本排序。距离数组是三维的,记录每个坐标在每个方向下的最小成本。 例如: import heapq def shortest_path(grid, start, end, k): # grid是二维数组,0表示可走,1表示障碍 # k是转向费用 dirs = [(-1,0), (1,0), (0,-1), (0,1)] # 上下左右,对应0,1,2,3 m, n = len(grid), len(grid[0]) sx, sy = start ex, ey = end # 初始化距离数组,dist[x][y][d]表示到达(x,y)方向d的最小成本 dist = [[[float('inf')] *4 for _ in range(n)] for __ in range(m)] heap = [] # 初始状态:从起点出发,四个方向都可能,但初始没有方向,所以第一次移动不需要转向费? # 假设初始时方向为-1,表示未移动过 for d in range(4): dx, dy = dirs[d] nx, ny = sx + dx, sy + dy if 0<=nx<m and 0<=ny<n and grid[nx][ny]==0: new_cost = 1 # 移动费用1,转向费用0(因为第一次) if dist[nx][ny][d] > new_cost: dist[nx][ny][d] = new_cost heapq.heappush(heap, (new_cost, nx, ny, d)) # 处理堆 while heap: cost, x, y, d = heapq.heappop(heap) if x == ex and y == ey: return cost if cost > dist[x][y][d]: continue # 尝试四个方向 for new_d in range(4): dx, dy = dirs[new_d] nx, ny = x + dx, y + dy if 0<=nx<m and 0<=ny<n and grid[nx][ny]==0: # 计算转向费用 turn_cost = 0 if new_d == d else k new_total = cost + 1 + turn_cost if new_total < dist[nx][ny][new_d]: dist[nx][ny][new_d] = new_total heapq.heappush(heap, (new_total, nx, ny, new_d)) # 如果无法到达 return -1 不过,这里可能需要调整初始状态的处理。比如,起点可能本身需要处理为初始状态。比如,如果起点是静止的,第一次移动可以选择四个方向,此时是否算转向?可能需要根据题目具体描述。比如,在起点时,方向为None,第一次移动时任何方向都不需要转向费用。因此,代码中的初始状态处理可能需要将起点的四个方向作为可能的移动,每个方向的总成本为移动费用1,转向费用0。 但上述代码中,初始循环是将起点周围的四个方向移动后的位置作为初始状态。这可能有问题,因为起点本身可能没有移动,所以初始处理可能需要从起点出发的第一步。例如,假设起点是(sx, sy),然后第一次移动是向四个方向中的一个,此时转向费用为0,因为之前没有方向。所以,初始循环应该处理这四个可能的方向,并将它们的初始成本设为1(移动费)+0(转向费)。 如果代码中的初始处理是正确的,那么当起点和终点相同时,返回0?或者需要特殊处理。例如,如果起点等于终点,直接返回0。但上述代码可能没有处理这种情况,需要加上。 此外,网格中的障碍物需要检查,例如,在移动时nx和ny必须不是障碍物。 现在,回到用户的问题,他们需要试题解析,所以可能需要更详细的步骤: 1. 题目分析:问题类型是最短路径,但带有转向费用。因此,每个移动的成本由移动步和转向决定。需要记录到达每个节点的方向,以计算转向费用。 2. 算法选择:Dijkstra算法,因为存在不同权重的边(转向费用导致路径成本不同)。 3. 状态设计:每个状态包括坐标(x,y)和当前方向d。距离数组dist[x][y][d]记录到达该状态的最小成本。 4. 状态转移:对于每个方向,计算新的成本,考虑是否转向,并更新距离数组。 5. 边界条件:起点和终点相同的情况,障碍物处理。 6. 复杂度分析:假设网格是M×N,每个节点有4个方向状态,总共有4MN个状态。优先队列的操作是O(4MN log(4MN))。 7. 可能的优化:如果转向费用k=0,则退化为普通BFS;若k很大,则算法可能优先选择转向少的路径。 现在,用户的问题可能需要示例输入和输出,比如,给出某个网格的输入,计算最短路径成本。例如: 样例输入: 网格大小3x3,起点(0,0),终点(2,2),无障碍,k=1。 可能的路径:直走两次右,两次下,总成本:移动4次,转向1次(比如右右右下下?或者路径是右右下下,移动次数4,转向1次,总成本4+1=5?或者可能不同的转向次数。) 不过,具体路径需要详细计算。例如,路径可能是右下右下,这样移动四次,转向三次?或者可能更优的路径是右右右下,移动三次,转向两次?可能需要具体分析。 总之,解析需要详细说明每个步骤,并给出代码框架,同时分析时间和空间复杂度。</think>### 2025钉耙编程春季联赛“航线”试题解析 #### 题目描述 在网格地图中,飞机从起点$(sx,sy)$飞往终点$(ex,ey)$,每次可以向**上下左右**四个方向移动一格,移动基础消耗$1$单位燃料。若飞行方向发生**转向**(与上一移动方向不同),需额外支付$k$单位转向燃料。求抵达终点的**最小总消耗**。 #### 解题思路 1. **问题建模** 将网格抽象为带权图,节点状态包含坐标$(x,y)$和当前方向$d$,边权由移动燃料$1$和转向燃料$k$共同决定。 2. **状态设计** 定义三维数组$dist[x][y][d]$,表示以方向$d$抵达$(x,y)$时的最小燃料消耗,其中$d \in \{0,1,2,3\}$分别对应上下左右四个方向。 3. **算法选择** 使用**Dijkstra算法**,优先队列按总燃料升序排列。每次扩展状态时,根据是否转向计算新路径的燃料消耗[^1]。 #### 核心代码框架 ```python import heapq def min_fuel(grid, start, end, k): dirs = [(-1,0), (1,0), (0,-1), (0,1)] # 上下左右 m, n = len(grid), len(grid[0]) sx, sy = start ex, ey = end # 初始化距离数组和优先队列 dist = [[[float('inf')]*4 for _ in range(n)] for __ in range(m)] heap = [] # 起点特殊处理:四个初始方向均无转向消耗 for d in range(4): dx, dy = dirs[d] nx, ny = sx + dx, sy + dy if 0<=nx<m and 0<=ny<n and grid[nx][ny] == 0: dist[nx][ny][d] = 1 heapq.heappush(heap, (1, nx, ny, d)) while heap: cost, x, y, d = heapq.heappop(heap) if (x, y) == (ex, ey): return cost if cost > dist[x][y][d]: continue # 尝试四个新方向 for new_d in range(4): dx, dy = dirs[new_d] nx, ny = x + dx, y + dy if 0<=nx<m and 0<=ny<n and grid[nx][ny] == 0: new_cost = cost + 1 + (k if new_d != d else 0) if new_cost < dist[nx][ny][new_d]: dist[nx][ny][new_d] = new_cost heapq.heappush(heap, (new_cost, nx, ny, new_d)) return -1 # 无法到达终点 ``` #### 复杂度分析 - **时间复杂度**:$O(MN \log(MN))$,其中$M \times N$为网格尺寸,每个节点最多有4个方向状态 - **空间复杂度**:$O(MN)$,用于存储三维距离数组和优先队列 #### 示例分析 假设输入网格为$3 \times 3$,起点$(0,0)$,终点$(2,2)$,$k=1$,无障碍物: 1. 路径①:右→右→下→下 - 转向次数:1次(右下转向) - 总消耗:$4 + 1 \times 1 = 5$ 2. 路径②:右→下→右→下 - 转向次数:3次 - 总消耗:$4 + 3 \times 1 = 7$ 显然路径①更优。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值