2025“钉耙编程”中国大学生算法设计暑期联赛(3) 1003 光线折射 题解记录

大致题意:在二维坐标系中,每个整点位置都有一垂直于y=−xy=-xy=x的玻璃,光线可折射和反射。折射率为aaa,反射率为bbb。现从(−0.5,0)(-0.5,0)(0.5,0)xxx轴正方向射出光强为111的光线,问(n+0.5,m)(n+0.5,m)(n+0.5,m)处的光强。
解:
x=n+1,y=mx=n+1,y=mx=n+1,y=m,期间共经过了x+yx+yx+y次玻璃。假设与玻璃接触后射向xxx轴正方向记为000,射向yyy轴正方向记为111,那么就可以得到一个以000结尾,xxx000yyy111组成的010101字符串。其中,相邻两个字符相同会贡献×a\times a×a,否则贡献×b\times b×b
现转化为:所有可能的字符串权值之和。
假设有k1k_1k1111段,k0k_0k0000段。
当起始位置为111的时候,形如1…01\dots 010,此时k0=k1k_0=k_1k0=k1。另,xxx000分成k0k_0k0段,会出现x−k0x-k_0xk0000000。所以此时权值为:ax−k1+y−k1×b2k1−1a^{x-k_1+y-k_1}\times b^{2k_1-1}axk1+yk1×b2k11,除此,由于是朝xxx轴正方向发射,故还会多出一个010101,即:ax+y−2k1×b2k1a^{x+y-2k_1}\times b^{2k_1}ax+y2k1×b2k1
当起始位置为000的时候,形如0…00\dots 000,此时k0=k1+1k_0=k_1+1k0=k1+1。算上初始发射时候的000000,权值为同样为ax+y−2k1×b2k1a^{x+y-2k_1}\times b^{2k_1}ax+y2k1×b2k1
从这里,我们可以开始考虑枚举k1k_1k1
如果起始位置为111,总的答案为:Cx−1k1−1×Cy−1k1−1×ax+y−2k1×b2k1C_{x-1}^{k_1-1}\times C_{y-1}^{k_1-1}\times a^{x+y-2k_1}\times b^{2k_1}Cx1k11×Cy1k11×ax+y2k1×b2k1。因为xxx000x−1x-1x1个空位,从这些空位选取k1−1k_1-1k11个截断会得到k1k_1k1个段。
如果起始位置为000,那么答案就是Cx−1k1×Cy−1k1−1×ax+y−2k1×b2k1C_{x-1}^{k_1}\times C_{y-1}^{k_1-1}\times a^{x+y-2k_1}\times b^{2k_1}Cx1k1×Cy1k11×ax+y2k1×b2k1
二者加起来得到最终式子:Cxk1×Cy−1k1−1×ax+y−2k1×b2k1C_{x}^{k_1}\times C_{y-1}^{k_1-1}\times a^{x+y-2k_1}\times b^{2k_1}Cxk1×Cy1k11×ax+y2k1×b2k1。接下来就枚举k∈[1,min(x,y)]k\in[1,min(x,y)]k[1,min(x,y)]
此时有一个需要注意的,如果每次都logloglog计算会tletletle。需要预处理出facfacfacinvfacinvfacinvfacinvinvinv。同时:记fi=Cxi×Cy−1i−1×ax+y−2i×b2if_i=C_{x}^{i}\times C_{y-1}^{i-1}\times a^{x+y-2i}\times b^{2i}fi=Cxi×Cy1i1×ax+y2i×b2i,那么fi+1=fi×(y−i)×(x−i)×inv(i)×inv(i+1)f_{i+1}=f_i\times (y-i)\times (x-i)\times inv(i)\times inv(i+1)fi+1=fi×(yi)×(xi)×inv(i)×inv(i+1),用此式子递推可以避免每次都暴力用快速幂计算ax+y−2ia^{x+y-2i}ax+y2ib2ib^{2i}b2i

#include<bits/stdc++.h>

#define endl '\n'
#define pii pair<int,int>
#define int long long
#define uint unsigned long long

using namespace std;

const double eps = 1e-10;
const int mod = 1e9 + 7;
const int inf = 1e18;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());

inline int sub(int a, int b) {
    a -= b;
    a %= mod;
    if (a < 0) return a + mod;
    else return a;
}

inline int add(int a, int b) {
    return (a + b) % mod;
}

inline int mul(int a, int b) {
    return a * b % mod;
}

inline int lowbit(int x) {
    return x & (-x);
}

inline int get(int x, int pos) {
    return (x >> pos) & 1;
}


inline int qp(int a, int b, int p) {
    if (b == 0) return 1;
    if (a == 0) return 0;
    int res = 1;
    while (b) {
        if (b & 1) {
            res = res % p * (a % p) % p;
        }
        b >>= 1;
        a = a % p * (a % p) % p;
    }
    return res % p;
}

inline uint rdm() {
    return rng();
}

const int N = 63;
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};


const int NN = 1000000;
vector<int> fac(NN + 4);
vector<int> invfac(NN + 4);
vector<int> inv(NN + 4);

void init() {
    fac[0] = 1;
    for (int i = 1; i <= NN; i++) {
        fac[i] = mul(fac[i - 1], i);
        inv[i] = qp(i, mod - 2, mod);
    }
    invfac[NN] = qp(fac[NN], mod - 2, mod);
    for (int i = NN - 1; i >= 0; i--) {
        invfac[i] = mul(invfac[i + 1], i + 1);
    }
}

int cnm(int n, int m) {
    if (m < 0 || m > n) return 0;
    return mul(fac[n], mul(invfac[m], invfac[n - m]));
}

void solve() {
    int n, m, c, d;
    cin >> n >> m >> c >> d;
    int x = n + 1, y = m;
    int ans = 0;
    int t = qp(c + d, mod - 2, mod);
    int a = mul(c, t), b = mul(d, t);
    int inva = qp(a, mod - 2, mod);

    if (y == 0) {
        cout << qp(a, x, mod) << endl;
        return;
    }

    if (b == 0) {
        cout << 0 << endl;
        return;
    }

    if (a == 0) {
        if (x != y) cout << 0 << endl;
        else cout << 1 << endl;
        return;
    }

    int ab = mul(mul(b, b), mul(inva, inva));
    int l = mul(x, mul(qp(a, x + y - 2, mod), qp(b, 2, mod)));
    for (int i = 1; i <= min(x, y); i++) {
        ans = add(ans, l);
        l = mul(l, sub(x, i));
        l = mul(l, sub(y, i));
        l = mul(l, mul(inv[i], inv[i + 1]));
        l = mul(l, ab);
    }

    cout << ans << endl;

}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T = 1;
    cin >> T;
    init();
    while (T--) {
        solve();
    }
}


<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(&#39;inf&#39;)] *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(&#39;inf&#39;)]*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$ 显然路径①更优。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值