题目链接:
[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倒是写的挺熟了,呜呜呜。