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;
}

浙公网安备 33010602011771号