1 条题解
-
0
一年半之前的模拟赛原题。
从部分分 出发,问题转换为确定一个长度为 的排列 ,满足 。这是一个非常经典的贪心:考虑依次确定值为 的位置,每次取出 最小的 ,若有多个 满足条件取 最小的,令 为当前的值。这个过程可以优先队列维护;若过程中出现了 不满足条件的情况,那么问题必然无解。
现在考虑如何使前 条边组成最小生成树。首先建出这棵树 ;令第 条边为 ,由于边权互不相同,所以对于一条不在 上的边 ,它的权值必然大于 上 路径上所有边权的最大值。
尝试进行一些操作:设 路径上的边分别为 ,可以令 $l_i\gets\max\left\{l_i,\left(\max\limits_{j=1}^kl_{p_j}\right)+1\right\}$(操作一),并且对于路径上每一条边 令 (操作二)。
根据最小生成树的性质可知,这样做仅仅筛掉了一些不合法的解,并不会把正确的解判漏;但是据此可以观察到一个非常有用的性质:进行完所有操作之后,对于所有不在 上的边 和 上 路径包含的某条边 ,都满足 !这意味着如果执行最开始的贪心流程,最终必然有 ,满足条件。
直接模拟上面的流程,操作一使用树剖 + ST 表维护链上最大值,操作二可以按照 从小到大覆盖到链上(转换为区间覆盖后用并查集维护)。令 同阶,时间复杂度为 。
放代码:
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> pii; typedef tuple<int,int,int> tpi; typedef tuple<int,int,int,int> edge; namespace IAOI_lib{ class dsu{ private: vector<int> a; public: dsu(int n):a(n){ iota(a.begin(),a.end(),0); } int leader(int x){ return a[x]==x?x:a[x]=leader(a[x]); } void merge(int x,int y){ x=leader(x),y=leader(y); if(x!=y)a[x]=y; } }; // 并查集模板 template<typename T,T(*op)(T,T)> class sparse_table{ private: vector<vector<T> > s; public: sparse_table(vector<T> a){ int k=__lg(a.size()); s.resize(k+1,vector<T>(a.size())); for(int i=0;i<a.size();i++) s[0][i]=a[i]; for(int i=1;i<=k;i++) for(int j=0;j+(1<<i)<=a.size();j++) s[i][j]=op(s[i-1][j],s[i-1][j+(1<<i-1)]); } T query(int l,int r){ int k=__lg(r-l+1); return op(s[k][l],s[k][r-(1<<k)+1]); } }; // ST 表模板 } vector<int> cover(int n,vector<tpi> v){ vector<int> f(n,-1); IAOI_lib::dsu d(n); for(auto [l,r,c]:v) for(int i=l;i<=r;i++){ if(~f[i])i=d.leader(i); else if(f[i]=c;i<n-1&&(i<r||~f[i+1]))d.merge(i,i+1); } return f; } // 区间覆盖(P2391),使用并查集维护 inline int mx(int x,int y){return x>y?x:y;} class hld{ private: int n; vector<vector<tpi> > g; vector<int> f,d,e,h,t,l; vector<tpi> q; optional<IAOI_lib::sparse_table<int,mx> > s; public: hld(vector<vector<tpi> > g) :n(g.size()),g(g),f(n),d(n),e(n,1),h(n,-1),t(n),l(n){ auto dfs=[&](auto &&self,int u)->void{ int mx=0; for(auto [i,p,w]:g[u]) if(i!=f[u]){ f[i]=u,d[i]=d[u]+1; self(self,i),e[u]+=e[i]; if(e[i]>mx)mx=e[i],h[u]=i; } }; int o=0; vector<int> lv(n); auto decomp=[&](auto &&self,int u,bool b)->void{ t[u]=b?t[f[u]]:u,l[u]=o++; if(~h[u])self(self,h[u],true); for(auto [i,p,w]:g[u]) if(i!=f[u]){ if(i!=h[u])self(self,i,false); lv[l[i]]=w; } }; f[0]=-1,dfs(dfs,0),decomp(decomp,0,false); s.emplace(lv); } int query(int u,int v,int c){ int w=-1; auto go=[&](int l,int r){ if(l>r)return; w=max(w,s->query(l,r)); q.emplace_back(l,r,c); }; while(t[u]!=t[v]){ if(d[t[u]]>d[t[v]])swap(u,v); go(l[t[v]],l[v]),v=f[t[v]]; } if(d[u]>d[v])swap(u,v); return go(l[u]+1,l[v]),w; } // 一条非树边造成的影响 vector<int> get_r(){ auto r=cover(n,q); vector<int> rs(n-1); auto dfs=[&](auto &&self,int u)->void{ for(auto [i,p,w]:g[u]) if(i!=f[u])rs[p]=r[l[i]],self(self,i); }; return dfs(dfs,0),rs; } // 求解所有操作二过后的 r }; // 树剖模板 int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin>>t; while(t--){ int n,m; cin>>n>>m; vector<edge> e(m); for(auto &[u,v,l,r]:e) cin>>u>>v>>l>>r,u--,v--,l--,r--; vector<vector<tpi> > t(n); vector<int> lv(n-1); for(int i=0;i<n-1;i++){ auto [u,v,l,r]=e[i]; lv[i]=l; t[u].emplace_back(v,i,l); t[v].emplace_back(u,i,l); } hld h(t); vector<int> p(m); iota(p.begin(),p.end(),0); sort(p.begin(),p.end(),[&](int x,int y){ return get<3>(e[x])<get<3>(e[y]); }); // 按照 r 排序,方便操作二 for(int i:p){ if(i<n-1)continue; auto &[u,v,l,r]=e[i]; l=max(l,h.query(u,v,r)+1); } // 枚举非树边进行操作 auto rs=h.get_r(); for(int i=0;i<n-1;i++){ auto &[u,v,l,r]=e[i]; if(~rs[i])r=min(r,rs[i]-1); } bool f=true; vector<tpi> c(m); for(int i=0;i<m&&f;i++){ auto [u,v,l,r]=e[i]; if(l>r||l>=m||r<0)f=false; c[i]=make_tuple(l,r,i); } sort(c.begin(),c.end()); priority_queue<pii,vector<pii>,greater<> > q; vector<int> w(m); for(int i=0,p=0;i<m&&f;i++){ while(p<m&&get<0>(c[p])==i){ auto [l,r,i]=c[p++]; q.emplace(r,i); } if(q.empty()||q.top().first<i)f=false; else w[q.top().second]=i,q.pop(); } // 贪心求解排列 w 的过程 if(f){ cout<<"Yes\n"; for(int i:w)cout<<i+1<<' '; cout<<'\n'; } else cout<<"No\n"; } return 0; }
- 1
信息
- ID
- 7096
- 时间
- 2500ms
- 内存
- 2048MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者