NOIP2014 day2 T2 洛谷P2296 寻找道路

本文介绍一种针对特定条件下的有向图寻找最短路径的算法,该算法首先进行反向广度优先搜索(BFS)以确定哪些节点可以直接或间接与终点相连,随后进行正向搜索来找出满足条件的最短路径。

题目描述

在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件:

1 .路径上的所有点的出边所指向的点都直接或间接与终点连通。

2 .在满足条件1 的情况下使路径最短。

注意:图G 中可能存在重边和自环,题目保证终点没有出边。

请你输出符合条件的路径的长度。

输入输出格式

输入格式:

输入文件名为road .in。

第一行有两个用一个空格隔开的整数n 和m ,表示图有n 个点和m 条边。

接下来的m 行每行2 个整数x 、y ,之间用一个空格隔开,表示有一条边从点x 指向点y 。

最后一行有两个用一个空格隔开的整数s 、t ,表示起点为s ,终点为t 。

输出格式:

输出文件名为road .out 。

输出只有一行,包含一个整数,表示满足题目᧿述的最短路径的长度。如果这样的路径不存在,输出- 1 。

输入输出样例

输入样例#1:
3 2  
1 2  
2 1  
1 3  
输出样例#1:
-1
输入样例#2:
6 6  
1 2  
1 3  
2 6  
2 5  
4 5  
3 4  
1 5  
输出样例#2:
3

说明

解释1:

如上图所示,箭头表示有向道路,圆点表示城市。起点1 与终点3 不连通,所以满足题

目᧿述的路径不存在,故输出- 1 。

解释2:

如上图所示,满足条件的路径为1 - >3- >4- >5。注意点2 不能在答案路径中,因为点2连了一条边到点6 ,而点6 不与终点5 连通。

对于30%的数据,0<n≤10,0<m≤20;

对于60%的数据,0<n≤100,0<m≤2000;

对于100%的数据,0<n≤10,000,0<m≤200,000,0<x,y,s,t≤n,x≠t。


先倒着BFS一遍,标记所有走过的点。

如果倒着BFS不能到起点,说明不连通。

如果能到起点,则正向BFS,SPFA求单源最短路径,途中要判断所经历的点都在答案路径内(即该点所有出边都指向之前标记过的点)

妥了

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
//
int head[500000];
int ct=0,s,t;
int used[300000],dis[300000];
int n,m,x[300000],y[300000];
//
//邻接表处理 
struct edge{
    int next;
    int to;
}e[500000];
void add(int from,int to){
    e[++ct].to=to;
    e[ct].next=head[from];
    head[from]=ct;
    return;
}
bool pd(int pos){
    int i;
    for(i=head[pos];i;i=e[i].next){
        if(!used[e[i].to])return 0;//未与终点联通 
    }
    return 1;
}
// 
int q[300000];
int main(){
    scanf("%d%d",&n,&m);
    int i,j;
    for(i=1;i<=m;i++){
        scanf("%d%d",&x[i],&y[i]);
        add(y[i],x[i]);//第一遍反向制表 
    }
    scanf("%d%d",&s,&t);
    //bfs
    int hd=0,tl=1;
    q[0]=t;
    used[t]=1;
    while(hd<=tl){
        int pos=q[hd];
        hd++;
        for(i=head[pos];i;i=e[i].next){
            if(!used[e[i].to]){
                q[++tl]=e[i].to;
                used[e[i].to]=1;
            }
        }
    }
    //finish
    if(!used[s]){
        printf("-1");
        return 0;
    }
    memset(head,0,sizeof(head));//再次初始化
    memset(q,0,sizeof(q));
    memset(dis,-1,sizeof(dis));
    ct=0;
    for(i=1;i<=m;i++){
        add(x[i],y[i]);
    }
    //bfs
    q[0]=s;
    dis[s]=0;
    hd=0;tl=1;
    int ans=10000;
    while(hd<=tl){//开始正向SPFA
        int pos=q[hd];
        hd++;
        if(pd(pos)==0)continue;
        for(i=head[pos];i;i=e[i].next){
            if(dis[e[i].to]    ==-1)
            {
                dis[e[i].to]=dis[pos]+1;
                q[tl++]=e[i].to;
                if(e[i].to==t){
                    ans=dis[t];
                    printf("%d",ans);//找到解输出
                    return 0;
                }
            }
        }
    }
    //finish
    printf("-1");
    return 0;
}


为你的学生设计一次 **3小时、4题** 的考试,需要兼顾以下几点: - 学生掌握:**基础语法(变量、循环、条件)、简单算法(模拟、枚举、排序、递归)、数据结构(二叉树)** - 未学内容:**图论(最短路、拓扑排序等)、高级数据结构(堆、并查集、线段树)、数论进阶** - 难度要求: - 不会爆零(T1要友好) - 有区分度(T3、T4要有一定思维量) - 时间合理(3小时内可完成2~3题为正常水平) --- 以下是我在洛谷中精心挑选的四道题目,按难度梯度排列,全部符合教学进度,并避免涉及图论: --- ### ✅ 推荐考试题目(洛谷) --- #### **T1:P1428 小鱼比可爱(签到题)** > 链接:[https://www.luogu.com.cn/problem/P1428](https://www.luogu.com.cn/problem/P1428) > **难度**:入门− / 普及− > **知识点**:数组遍历、简单比较 **题目大意**:给定一个序列,对每个位置 i,统计前面有多少个数小于 `a[i]`。 ```python n = int(input()) a = list(map(int, input().split())) res = [] for i in range(n): cnt = 0 for j in range(i): if a[j] < a[i]: cnt += 1 res.append(cnt) print(' '.join(map(str, res))) ``` > ✅ 特点:逻辑清晰,适合练基础循环和数组操作,几乎不会错,防止爆零。 --- #### **T2:P1427 小鱼的航程(改进版)** > 链接:[https://www.luogu.com.cn/problem/P1427](https://www.luogu.com.cn/problem/P1427) > **难度**:普及− > **知识点**:模拟、分段计算、数学建模 **题目大意**:小鱼游泳,周一到周四每天游 `x` km,周五到周日不游。问从某天开始游 `n` 天后共游了多少公里? ```python x, n = map(int, input().split()) total = 0 # 把 n 天拆成完整周 + 剩余天数 weeks = n // 7 remainder = n % 7 total += weeks * 4 * x # 每周游4天 # 加上剩余天数中的工作日(注意不能超过周四) start_day = 1 # 假设从周一出发(题目默认从第一天开始) for i in range(remainder): current_day = (start_day + i - 1) % 7 + 1 # 1=周一, ..., 7=周日 if 1 <= current_day <= 5: # 周一到周五 if current_day <= 4: # 只有周一到周四游 total += x # else: 周六周日不游 print(total) ``` > 更简洁写法(直接判断剩余天落在哪几天): ```python x, n = map(int, input().split()) ans = (n // 7) * 4 * x r = n % 7 # 剩余 r 天中,最多前 min(r, 4) 天是有效 if r > 4: ans += 4 * x else: ans += r * x print(ans) ``` > ✅ 特点:需要一点生活化建模能力,但无复杂算法,中等偏易。 --- #### **T3:P1305 新二叉树(经典构造题)** > 链接:[https://www.luogu.com.cn/problem/P1305](https://www.luogu.com.cn/problem/P1305) > **难度**:普及/提高 > **知识点**:二叉树重建、递归或迭代建树、先序遍历输出 **题目大意**:给定多个形如 `(A,B)` 的节点关系,表示 A 是父节点,B 是左/右儿子。最终输出先序遍历结果。 > 注意:输入格式是括号表示法,例如 `(A,B)` 表示 B 是 A 的左儿子;`(A,)` 表示没有右儿子。 **解法思路**:用字典记录每个节点的左右孩子,然后从根开始递归遍历。 ```python import sys input = sys.stdin.read data = input().strip().replace('(', ' ').replace(')', ' ').split() n = len(data) // 2 tree = {} # 根节点是第一个出现的父亲 root = data[0] children = {} for i in range(0, len(data), 2): p = data[i] left = data[i+1] if i+1 < len(data) and data[i+1] != '' else None if p not in children: children[p] = [None, None] if left != '': children[p][0] = left # 左儿子 # 右儿子处理(下一个如果是空字符串,则是右缺失) if i+2 < len(data) and data[i+2] == p: # 下一个是同一父亲? continue # 实际上输入是顺序的,我们可以在读取时统一处理 # 重新解析更安全的方式:逐个读取原始括号?但本题输入保证合法 # 简化做法:我们只记录所有出现过的子节点,根就是没被当过子的节点 nodes = set() child_set = set() i = 0 while i < len(data): p = data[i]; i += 1 l = data[i]; i += 1 if p not in children: children[p] = [None, None] if l != '': children[p][0] = l child_set.add(l) if i < len(data) and data[i] == p: i += 1 r = data[i]; i += 1 if r != '': children[p][1] = r child_set.add(r) # 找根节点:出现在 parents 中但不在 child_set 中的第一个 all_parents = set(children.keys()) root = (all_parents - child_set).pop() # 先序遍历 def preorder(node): if node is None: return '' return node + preorder(children.get(node, [None,None])[0]) + \ preorder(children.get(node, [None,None])[1]) print(preorder(root)) ``` > ⚠️ 提示:此题输入较恶心,建议提醒学生使用标准方法解析。也可以改用 `eval` 或正则,但在比赛中不推荐。 > ✅ 特点:考察对二叉树结构的理解与构建能力,有一定编码难度。 --- #### **T4:P1028 [NOIP2001 普及组] 数的计算** > 链接:[https://www.luogu.com.cn/problem/P1028](https://www.luogu.com.cn/problem/P1028) > **难度**:普及+/提高 > **知识点**:递推 / 记忆化搜索、数学规律 **题目大意**:对于一个自然数 `n`,可以依次在它左边加上不超过它的正整数,且加上的数也不超过其左边最近数的一半。求总共能生成多少个数字(含原数)。 例如:`n=6` → 可以加 1~3,在 3 左边可加 1,形成 `136`,等等。 **解法:递推(DP)** 设 `f[n]` 表示以 `n` 开头时,总共能组成的方案数(包括自身)。 则: ```text f[1] = 1 f[n] = 1 + f[1] + f[2] + ... + f[n//2] ``` 因为可以在 n 左边放 1 到 n//2,每种都带来 f[i] 种后续组合。 ```python n = int(input()) f = [0] * (n + 1) for i in range(1, n + 1): f[i] = 1 # 自身 for j in range(1, i // 2 + 1): f[i] += f[j] print(f[n]) ``` > ✅ 特点:不需要高深知识,但需要发现递推关系,适合锻炼思维。 --- ### 📌 考试整体安排总结 | 题号 | 编号 | 名称 | 难度星级 | 预计得分率 | 考察重点 | |------|----------|------------------|----------|------------|------------------------| | T1 | P1428 | 小鱼比可爱 | ★★☆☆☆ | 90%+ | 数组、模拟、比较 | | T2 | P1427 | 小鱼的航程(改进) | ★★★☆☆ | 70%+ | 分段模拟、数学建模 | | T3 | P1305 | 新二叉树 | ★★★★☆ | 50%+ | 二叉树构建、遍历 | | T4 | P1028 | 数的计算 | ★★★★☆ | 40%+ | 递推 / 记忆化搜索 | > ✅ 总体评价: - 四道题均来自 NOIP 真题或经典改编,质量高; - 无图论、无高级算法; - T1保底不爆零,T4拉开差距; - 3小时内做完两题属良好,三题以上优秀。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值