洛谷P4742[Wind Festival]Running In The Sky题解

本文详细介绍了如何运用Tarjan算法解决一道关于有向图中寻找最大权值路径的问题。作者通过三天的努力,理解并应用Tarjan算法进行强连通分量的查找,并在此基础上进行拓扑排序和动态规划,最终求解出图中总权值最大且单点权值最大的路径。文章还提供了tarjan算法的模板和解题思路,以及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:

[P4742 Wind Festival]Running In The Sky - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

考点:

dp,拓扑排序,tarjan缩点

这一道题我写了整整三天,三天啊,你知道我这三天怎么过的吗?!

题意:一张有向图,每个点有一个权值,计算图中总权值最大的路径(图中可能有环,重复经过同一个点只计算一次权值),并计算这条路径中最大的单点权值是多少。

第一天:硬写拓扑,自然是wa,因为有环,所以去学了tarjan,但是还是wa

第二天:感觉可能是自己对tarjan不熟悉,于是去刷了几道tarjan的题,回来再debug,还是Wa

第三天:急眼了,重写了好几遍,终于有一遍过了,然后发现原来是自己存边两个边数组开小了,但是它报的wa就很离谱,我人傻了。

思路:首先由于题目中说有环,这种有环还要用拓扑的,一般都是采用tarjan缩点,那什么是tarjan呢?可以参考下面这篇博客:

图论——强连通分量(Tarjan算法)上总介的博客-优快云博客强连通分量

我也说说我对tarjan的理解,tarjan就是利用dfs序去检验某个点是否在向下dfs的时候遇到了某个已经访问过的点,如果遇到了,那这时候就一定出现了一个环,于是我们给其打上和那个已经访问过的点一样的标记并在回溯的过程中将该点到那个已访问过的点之间的路径上的点全都打上相同的标记,这样就找出了一个环,别看变量很多,其实思想真的不复杂。

tarjan模板:

 int dfn[maxn];
 int low[maxn];
 int vis[maxn];
 int color[maxn];
 int dn=0,c=0;
 stack<int>s;
 void tarjan(int x){
     dfn[x]=low[x]=++dn;
     vis[x]=1;
     s.push(x);
     //我喜欢用初始化成-1的链式前向星所以这样写
     for(int i=head[x];i!=-1;i=edge[i].ne){
         int son=edge[i].to;
         if(dfn[son]==0){
             tarjan(son);
             low[x]=min(low[son],low[x]);
         }else if(vis[son]==1){
             low[x]=min(low[x],dfn[son]);
         }
     }
     if(low[x]==dfn[x]){
         c++;
         while(1){
             int t=s.top();
             s.pop();
             color[t]=c;
             vis[t]=0;
             if(t==x){
                 break;
             }
         }
     }
 }

知道了tarjan算法那这个题就很简单了,我们只需要用tarjan算法找到所有的强连通分量并将他们合并在一起当作一个新的点,并维护出这个新点的权值和最大单点权值,然后重新建图,在这个新图上跑拓扑序dp维护两个答案就行

对于最大路径总权值我们只要发现有更长的就更新就可以。

但是对于最大单点权值有两种情况:

1-当前路径最大权值比之前保存的更长,这时最大单点权值=max(val[son],ans[x]),即当前结点权值或者前面路径上的最大值

2-当前路径最大权值和保存的相等,此时最大单点权值=max(ans[son],ans[x]),即两条路径所有点的最大单点权值。

 #include <bits/stdc++.h>
 const int maxn=200010;
 using namespace std;
 struct node{
     int ne,to;
 };
 //原图: 
 node edge[500010];
 int head[maxn];
 int cnt=0;
 void addedge(int a,int b){
     cnt++;
     edge[cnt].ne=head[a];
     edge[cnt].to=b;
     head[a]=cnt;
 }
 //新图: 
 node n_edge[500010];
 int n_head[maxn];
 int n_cnt=0;
 void n_addedge(int a,int b){
     n_cnt++;
     n_edge[n_cnt].ne=n_head[a];
     n_edge[n_cnt].to=b;
     n_head[a]=n_cnt;
 }
 //结点值 
 int val[maxn];
 //缩点后的总价值和最大单价 
 int ne_val[maxn],ma[maxn];
 //tarjan: 
 int vis[maxn],dfn[maxn],low[maxn],color[maxn],dn,c;
 stack<int>s;
 //tuopu:
 int ru[maxn]; 
 //答案:
 int ans_dis[maxn],ans_ma[maxn];
  
 void tarjan(int x){
     dfn[x]=low[x]=++dn;
     vis[x]=1;
     s.push(x);
     for(int i=head[x];i!=-1;i=edge[i].ne){
         int son=edge[i].to;
         if(dfn[son]==0){
             tarjan(son);
             low[x]=min(low[x],low[son]);
         }else if(vis[son]==1){
             low[x]=min(dfn[son],low[x]);
         }
     }
     if(dfn[x]==low[x]){
         c++;
         while(1){
             int t=s.top();
             s.pop();
             ne_val[c]+=val[t];
             ma[c]=max(ma[c],val[t]);
             color[t]=c;
             vis[t]=0;
             if(t==x){
                 break;
             }
         }
     }
 }
 ​
 void tuopu(){
     queue<int>q;
     for(int i=1;i<=c;i++){
         if(ru[i]==0){
             q.push(i);
         }
         ans_dis[i]=ne_val[i];
         ans_ma[i]=ma[i];
     }
     while(!q.empty()){
         int t=q.front();
         q.pop();
         for(int i=n_head[t];i!=-1;i=n_edge[i].ne){
             int son=n_edge[i].to;
             if(ans_dis[son]<ans_dis[t]+ne_val[son]){
                 ans_dis[son]=ans_dis[t]+ne_val[son];
                 ans_ma[son]=max(ma[son],ans_ma[t]);
             }else if(ans_dis[son]==ans_dis[t]+ne_val[son]){
                 ans_ma[son]=max(ans_ma[son],ans_ma[t]);
             }
             if(--ru[son]==0){
                 q.push(son);
             }
         }
     }
 }
 ​
 int main(){
     memset(head,-1,sizeof(head));
     memset(n_head,-1,sizeof(n_head));
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>val[i];
     }
     for(int i=1;i<=m;i++){
         //这里我之前写的都是cin>>a[i]>>b[i]
         //然后a,b开小了,我吐了呀
         int a,b;
         cin>>a>>b;
         addedge(a,b);
     
     }
     //这个图不一定是联通的,所以要循环枚举
     for(int i=1;i<=n;i++){
         if(dfn[i]==0){
             tarjan(i);
         }
     }
     //重新建图
     for(int i=1;i<=n;i++){
         for(int j=head[i];j!=-1;j=edge[j].ne){
             int son=edge[j].to;
             int fa1=color[i];
             int fa2=color[son];
             if(fa1!=fa2){
                 n_addedge(fa1,fa2);
                 ru[fa2]++;
             }
         }
     } 
     tuopu();
     int ans1=0,ans2=0;
     for(int i=1;i<=c;i++){
         if(ans_dis[i]>ans1){
             ans1=ans_dis[i];
             ans2=ans_ma[i];
         }else if(ans_dis[i]==ans1){
             ans2=max(ans2,ans_ma[i]);
         }
     }
     cout<<ans1<<" "<<ans2<<endl;
 }

洛谷!你为什么不给我报re啊,你要是报了re我不早a了嘛!!!!

结果就是被迫写了五六遍这题,tarjan倒是写的挺熟了,呜呜呜。

### 关于 P1036 的 Java 解法 对于 P1036 题目,其核心在于模拟操作以及字符串处理。以下是基于题目描述的分析与解决方案。 #### 问题解析 该题的核心目标是对输入数据进行特定规则下的转换或计算。通常情况下,这类问题可以通过逐字符遍历或者利用数组来完成逻辑上的判断和更新[^3]。由于具体题目细节未提供,这里假设问题是关于某种模式匹配或简单算法的应用场景。 #### 实现方法 采用循环结构配合条件语句可以有效解决此类问题。下面是一个可能的实现方式: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 输入整数n表示测试组数 while (n-- > 0) { // 处理每组测试数据 String s = scanner.next(); // 初始化变量用于存储结果或其他中间状态 StringBuilder result = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // 根据题目需求执行相应逻辑 if (c >= 'A' && c <= 'Z') { result.append((char)(c + 32)); // 转换大小写作为例子 } else { result.append(c); // 不变的情况 } } System.out.println(result.toString()); } scanner.close(); } } ``` 上述代码展示了如何通过读取多组输入并逐一处理的方式解决问题。此模板适用于许多涉及字符串变换的操作类题目[^4]。 #### 注意事项 当使用Java提交时需注意效率优化,尤其是面对大数据量的情况下。例如,在某些极端条件下,StringBuilder相比String能带来更优性能表现因为前者减少了不必要的对象创建开销[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuhudaduizhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值