AtCoder ABC335 E - Non-Decreasing Colorful Path(拓扑排序、并查集)

题意

给一张 N N N个点, M M M条边的无向图,每个点有点权,问从顶点 1 1 1到顶点 N N N满足经过点的点权不减的路径中,不同点权的数量最多是多少。

分析和错误示范

PS:这题是见过的细节超多的一道题,在看了题解知道思路后,自己实现,还能wa一页,非常离谱,这里主要讲一下我遇到的坑有哪些。
首先题目要求路径上点权不减,很明显可以在建图时只保留点权小到点权大的边,这样随便的路径都能够满足点权不减的要求,难在维护路径上不同点权的数量。

1.TLE做法

很容易想到,可以利用 d p dp dp 的方式来维护,第一个点的 d p dp dp 值为1,然后向与它相邻的点更新 d p dp dp 值,如果相邻的点权值相等,则 d p dp dp 值用原先的更新,否则用原先的 d p dp dp 值+1更新,这样就可以用拓扑排序bfs慢慢跑完更新,就荣获一个TLE。

2.WA做法

赛后忽然想到,我那样bfs会TLE的原因在于没有缩点,而对联通的点权相同的点来说是显然的,因为路径上点权递增,所以对缩完的点来说,如果走出去了,那么就肯定不用再回头,所以这些点只会最多经过一次,当然内部之间可以随便跳,所以可以缩点。

wa1

缩点的方法,我一开始懒得写并查集,就想直接用一个数组记一下映射,第一次出现点权的点当作父节点,然后缩点。
然后wa了2次之后,才想起来我这个毛病犯得很粗暴,那就是我变成在开始读入点权时候就缩点了,其中可能有的点权相同的点并不连通,所以不能缩。所以还是需要使用并查集,那没办法了,改!于是喜提wa2

wa2

使用并查集,并且离线建边,在缩点后重新建边,再次跑拓扑排序,依旧wa。然后发现是我初始化结点1的 d p dp dp 值忘记修改为初始化1号结点的父亲的 d p dp dp 值,所以错了。而且拓扑排序有可能是从其他入度为0的结点开始走,所以还需要其他结点的 d p dp dp 值设为负无穷,最后输出时跟0取个max。再改!于是喜提wa3

wa3

这个wa3特别有意思,我正在洛谷的评论区看别人代码找不同时,注意到了一个地方,还在评论区提问了一手。那时的我,并没有意识到那个不起眼的操作是什么意思,作者题解里也没有解释。直到我再次wa到天荒地老时,我才忽然意识到。
在拓扑排序初始入度为0的结点入队后,需要修改它们的入度,否则因为是遍历所有的 i i i ,就导致缩了的入度为0点会多次入队,从而影响拓扑排序的结果。所以入队后需要修改结点的入度。改了这个之后,慢慢一页wa和t,总算以ac告终。

总结

本题注意到可以缩点的性质后,对边进行离线,缩点后重新建图,并且要留意缩点前后的关系,而且拓扑排序时入队后结点的入度要更改,防止同个缩点多次入队。

代码

#include<bits/stdc++.h>
//#define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int,int>
#define db double
using namespace std;
const int maxn=2e5+10;
const int mod=998244353;
int val[maxn],dp[maxn],fa[maxn],in[maxn];
vector<int>G[maxn];
vector<pii>temG;
int Find(int x){return fa[x]==x?fa[x]:fa[x]=Find(fa[x]);}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>val[i];
        fa[i]=i;
        dp[i]=-inf;
    }
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        u=Find(u),v=Find(v);
        if(u==v) continue;
        if(val[u]==val[v]){
            fa[u]=v;
            continue;
        }
        temG.push_back(pii(u,v));
    }
    for(auto it:temG){
        int u=it.first,v=it.second;
        u=Find(u);v=Find(v);
        if(val[u]>val[v]) G[v].push_back(u),in[u]++;
        if(val[v]>val[u]) G[u].push_back(v),in[v]++;
    }
    queue<int>q;
    dp[Find(1)]=1;
    for(int i=1;i<=n;i++)
        if(!in[Find(i)]) q.push(Find(i)),in[Find(i)]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(auto v:G[u]){
            dp[v]=max(dp[v],dp[u]+1);
            in[v]--;
            if(!in[v]) q.push(v);
        }
    }
    cout<<max(dp[Find(n)],0)<<endl;
    //system("pause");
    return 0;
}
  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值