CF495E题解

Pashmak and Graph

题面翻译

给定 n n n 个点, m m m 条带权边的有向图。
现在请你找一条路径,起点和终点自取,在保证路径上的边权严格递增(即
下一条边的v 严格大于上一条的v)的情况下包含最多的边。
每条边只用一次。请输出路径最多能包含多少条边。

第一行输入2个数字 n n n , m m m , 表示 n n n 个点 m m m 条有向边。
第2 到 m + 1 m+1 m+1 行每行3 个数 s , t s,t s,t v v v ,表示边的起点、终点、边权。

样例 #1

样例输入 #1

3 3
1 2 1
2 3 1
3 1 1

样例输出 #1

1

样例 #2

样例输入 #2

3 3
1 2 1
2 3 2
3 1 3

样例输出 #2

3

样例 #3

样例输入 #3

6 7
1 2 1
3 2 5
2 4 2
2 5 2
2 6 9
5 4 3
4 3 4

样例输出 #3

6

首先我们考虑一下我们是如何找到一条合法的路径的。当我们找到了一条边,现在考虑这个边的合法的路径就一定是一个和当前这条边的终点的连结的边,同时这个边满足权值严格大于选定的这条边。

然后发现这个数据范围是 ( 2 < = n < = 3 ⋅ 1 0 5 ; 1 < = m < = m i n ( n ⋅ ( n − 1 ) , 3 ⋅ 1 0 5 ) ) (2<=n<=3·10^{5}; 1<=m<=min(n·(n-1),3·10^{5})) (2<=n<=3105;1<=m<=min(n(n1),3105)) 。那么我们就肯定是不能暴力的去枚举,然后更新一下每条边的路径。

我们会发现一个性质就是大的边的路径的长度一定是从小的边更新得来的,那么我们如果是将每条边按照权值从小到大排一下序,从头到尾枚举每条边然后只更新一下出点的长度,那么当我们后面的边再遇到了这个点的时候,所有关于这个点的所有的合法的边就肯定都已经被更新了。(可以感性理解一下,应该非常好理解)

那么我们现在只需要怎么转移即可了。其实这一步也非常简单。对于一个出点,他的值要么是走当前这条边,要么是从别的边转移过来。那么我们的转移方程就出来了。 f v = m a x ( f v , f u + 1 ) f_v = max(f_v, f_u + 1) fv=max(fv,fu+1)。其中 v v v 是出点, u u u 是入点。

还有一个小细节是我们这个图是可能会有重边的,那么我们可以直接开一个临时数组,把他们先存一下,然后一起转移即可。

下面是代码实现

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
        x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}

inline void write(int x)
{
    if(x < 0)
    {
        x = -x;
        putchar('-');
    }
    if(x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

const int N = 1e6 + 10;

int n, m;
int f[N], g[N];   //f[]: 答案  g[]:重边的处理
int stk[N], cnt;    //存放重边
int res = 0;

struct Edge
{
    int u, v, w;
    bool operator < (const Edge& a) {return w < a.w;}
} e[N];

int main()
{
    n = read(), m = read();

    for(register int i = 1 ; i <= m ; i ++ )
    {
        e[i].u = read(), e[i].v = read(), e[i].w = read();
    }

    sort(e + 1, e + m + 1);

    for(register int i = 1 ; i <= m ; i ++ )
    {   
        int j = i - 1;  //到最左边的相同的边的位置
        while(e[++ j].w == e[i].w)   //将所有重边进行转移
        {
            stk[++ cnt] = e[j].v;
            g[e[j].v] = max(g[e[j].v], f[e[j].u] + 1);   //临时存一下重边的状态
        }
        while(cnt)
        {
            f[stk[cnt]] = max(f[stk[cnt]], g[stk[cnt]]);   //用重边更新当前枚举的边
            g[stk[cnt]] = 0;  //清空临时数组
            cnt --;
        }
        i = j - 1;   //因为我们已经处理了所有的重边,那么就直接跳过这些重边即可
    }

    for(register int i = 1 ; i <= n ; i ++ )
    {
        res = max(res, f[i]);
    }

    write(res);

    return 0;
}

又水了一道题。★,°:.☆( ̄▽ ̄)/$:.°★

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值