Pathwalks: 图上的LIS问题

题目链接:https://codeforces.com/contest/960/problem/F

解法一 贪心

回顾\(LIS\)问题,我们维护\(dp[i]\)表示长度为\(i\)的上升子序列中,可能的末尾元素的最小值。这个贪心的核心在于维护末尾的最小值,使得尽可能允许新加入的元素能够构成更长的序列。

首先,本题没法对每个点开\(10^5\)的数组,因此考虑对每个点开\(map\)。其次,\(dp\)是把长度作为\(index\),但由于\(map\)没法对\(value\)做二分,只能按\(key\)查询,所以需要把\(weight\)作为\(key\)
因此定义\(map[i][j]\)表示到达第\(i\)个点的路径中,末尾权值为\(j\)的最长路径。至于编号要按升序,只需要按照插入的时间戳依次操作,就相当于操作第\(i\)条边时,所有编号在后面的边不存在。

接下来考虑实现,本题无法做到每次插入一条边,只更改一个元素,例如插入某条边时,出现了一条权值特别小、长度特别大的路径,那么指向的点对应的\(map\)很可能要大改。
假设插入的边为\(\{u, v, w\}\),首先要和到达\(u\)的路径连起来构成更长的路径,也就是查询\(\mathop{max}\limits_{k<w} (map[u][k])\),暴力查询不可行,实际上只要保证\(map[i][j]\)的单调性,就只需要查询最大的\(k\)即可。
考虑贪心,如果某条路径的末尾权值为\(w\),如果存在更长、权值更小的路径,那么这条路径就不应当被插入。否则考虑所有比它短的、末尾权值大于等于\(w\)的路径,它们不可能再被选中,因此这些路径可以直接删掉。这样就保证了单调性。

如果不删除“所有比它短的、末尾权值大于等于\(w\)的路径”,而是将其值改为新路径的长度,会导致复杂度爆炸。删除它们即可保证每条边最多插入一次、删除一次,复杂度\(O(m\cdot log(max(w)))\)

#include<bits/stdc++.h>
using namespace std;
using ll =long long;
ll n,m;
const ll N=1e5+5;
map<ll,ll> mp[N];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>n>>m;
    ll ans=0;
    for(ll i=1;i<=m;++i){
        ll u,v,w;
        cin>>u>>v>>w;
        auto it=mp[u].lower_bound(w);
        ll len=1;
        if(it!=mp[u].begin()) {
            it--;
            len=it->second+1;
        }
        it=mp[v].lower_bound(w);
        if(it!=mp[v].begin()){
            auto tmp=it;
            tmp--;
            if(tmp->second>=len) continue;
        }
        while(it != mp[v].end()){
            if(it->second<=len) {
                auto tmp=it;
                ++tmp;
                mp[v].erase(it);
                it=tmp;
            }
            else{
                it++;
            }
        }
        if(mp[v].find(w) == mp[v].end()) mp[v][w]=len;
        ans=max(ans, mp[v][w]);
    }
    cout<<ans;
    return 0;
}

解法二 权值线段树

暴力查询查询\(\mathop{max}\limits_{k<w} (map[u][k])\)不可行的话,打个权值线段树查询就行了。动态开点保证复杂度,复杂度同样为\(O(m\cdot log(max(w)))\)

#include<bits/stdc++.h>
using namespace std;
using ll =long long;
ll n,m;
const ll N=1e5+5;
ll tree[N<<5], ls[N<<5], rs[N<<5], rt[N];
ll cnt=0;
void update(ll x, ll y, ll p, ll pl, ll pr) {
    if (pl==pr){
       tree[p]=y; 
       return;
    }
    ll mid=pl+pr>>1;
    if(x<=mid) {
        if(!ls[p]) ls[p]=++cnt;
        update(x, y, ls[p], pl, mid);
    }
    else {
        if(!rs[p]) rs[p]=++cnt;
        update(x, y, rs[p], mid+1, pr);
    }
    tree[p]=max(tree[ls[p]], tree[rs[p]]);
}
ll query(ll L, ll R, ll p, ll pl, ll pr) {
    if(pl>=L && pr<=R){
        return tree[p];
    }
    ll mid=pl+pr>>1;
    ll tmp=0;
    if(L<=mid && ls[p]){
        tmp=query(L, R, ls[p], pl, mid);
    }
    if(R>mid && rs[p]){
        tmp=max(tmp, query(L, R, rs[p], mid+1, pr));
    }
    return tmp;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>n>>m;
    ll upper=N-4;
    ll ans=0;
    for(ll i=1;i<=m;++i){
        ll u,v,w;
        cin>>u>>v>>w;
        w++; // 权值范围改为[1, 1e5+1] 不改也完全没问题
        if(!rt[u]) rt[u]=++cnt;
        if(!rt[v]) rt[v]=++cnt;
        if(w==1){
            update(1, 1, rt[v], 1, upper);
        }
        else {
            ll tmp=query(1, w-1, rt[u], 1, upper);
            if(tmp>=query(1,w,rt[v],1,upper)){
                // 如果tmp>=query(w,w,...) 但是<query(1,w,...) 就没有必要更新w位置的值
                update(w,tmp+1,rt[v],1,upper);
            }
        }
        ans=max(ans, query(1, upper, rt[v], 1, upper));
    }
    cout<<ans;
    return 0;
}
posted @ 2025-06-22 19:14  TimeLimit  阅读(30)  评论(0)    收藏  举报