题目描述
给定 (n) 个点 (m) 条边的有向图,可能不连通,可能有重边,也可能会有自环。求最长的路径(可以经过重复节点),使得这条路径的编号和权值都严格单调递增,其中编号指输入的顺序。路径的长度是指经过边的数量。
输入格式
第一行两个整数 (n,m)。
第二行到第 (m+1) 行,每行三个整数 (a,b,k),表示顶点 (a) 与顶点 (b) 有一条边相连,边权为 (k)。
输出格式
一行一个整数,表示最长的路径的长度。
(1leq n,m,w_ileq10^5)。
考虑直接dp,设(f_i)表示前(i)条边其中保留第(i)条边时的答案,那么有转移方程如下:
[f_i=max_{j=1}^{i-1}f_j+1[k_j<k_i,b_j=a_i]
]
直接做是(O(n^2))的,那么考虑优化。
观察这个式子我们可以发现,我们需要维护(N)个数组,每次查询一个数组里的前缀最大值,往一个数组里插入一个数,那么这个东西可以用动态开点线段树实现。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 1e5;
using namespace std;
struct edges
{
int u,v,w;
}edge[N + 5];
int n,m,f[N + 5],ans,rt[N + 5];
struct Seg
{
int mx[N * 20 + 5],lc[N * 20 + 5],rc[N * 20 + 5],node_cnt;
void ins(int &k,int l,int r,int x,int v)
{
if (!k)
k = ++node_cnt;
mx[k] = max(mx[k],v);
if (l == r)
return;
int mid = l + r >> 1;
if (x <= mid)
ins(lc[k],l,mid,x,v);
else
ins(rc[k],mid + 1,r,x,v);
}
int query(int k,int l,int r,int x)
{
if (!k)
return 0;
if (r <= x)
return mx[k];
int mid = l + r >> 1;
if (x <= mid)
return query(lc[k],l,mid,x);
else
return max(query(lc[k],l,mid,x),query(rc[k],mid + 1,r,x));
}
}tree;
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1;i <= m;i++)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w),edge[i].w++;
ans = f[1] = 1;
tree.ins(rt[edge[1].v],1,N + 1,edge[1].w,f[1]);
for (int i = 2;i <= m;i++)
{
f[i] = 1;
f[i] = tree.query(rt[edge[i].u],1,N + 1,edge[i].w - 1) + 1;
ans = max(ans,f[i]);
tree.ins(rt[edge[i].v],1,N + 1,edge[i].w,f[i]);
}
cout<<ans<<endl;
return 0;
}