1 条题解
-
0
为什么没人写分治呢?
首先肯定要拓扑排序。
定义两端拓扑序分别为 的边为 ,以拓扑序为 的点为开头 / 结尾的最长路分别为 。
因为要求删掉一个点后的答案,考虑缺一分治。
对拓扑序分治,当递归到 时,我们需要求出 、 与当前的最长路。
显然,当前的最长路分为左半边的最长路、右半边的最长路与跨过区间的最长路,其中左右半边的最长路可以在递推时顺便求出,跨过区间的最长路则需要枚举每一条跨过区间的边 ,用 更新答案。
设中点为 ,当将要递归到 时先递推求出 ,然后用 更新单边最长路,并用边 更新跨过区间的最长路。
递归到 的部分同理,容易证明正确性。
因为每个点和每条边都会被遍历 次,因此时间复杂度 。
代码,感觉比其他做法都好写:
#include<bits/stdc++.h> using namespace std; typedef long long ll; int n,m,x,ma,a,ans=2147483647; vector<int> t[500005][2]; queue<int> q; int ord[500005],id[500005],in[500005],len,dis[500005]; int st[500005],top; void solve(int l,int r,int s){ if(l==r){ if(s<ans)ans=s,a=ord[l];//更新答案 return; } int ls=s,mid=(l+r)>>1;//存储开始时的答案方便还原 for(int i=l;i<=mid;++i){ for(auto j:t[ord[i]][1])dis[ord[i]]=max(dis[ord[i]],dis[j]+1),s=max(s,dis[ord[i]]);//递推求最长路并求单边答案 for(auto j:t[ord[i]][0]) if(id[j]>r)s=max(s,dis[ord[i]]+dis[j]+1);//计算跨过当前区间的答案 } solve(mid+1,r,s); s=ls;//还原答案 for(int i=l;i<=mid;++i)dis[ord[i]]=0;//因为这个区间之前没被用过,所以全部还原为0 for(int i=r;i>mid;--i){ for(auto j:t[ord[i]][0])dis[ord[i]]=max(dis[ord[i]],dis[j]+1),s=max(s,dis[ord[i]]); for(auto j:t[ord[i]][1]) if(id[j]<l)s=max(s,dis[ord[i]]+dis[j]+1); } solve(l,mid,s); for(int i=mid+1;i<=r;++i)dis[ord[i]]=0; } int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>m; for(int i=1,x,y;i<=m;++i) cin>>x>>y,t[x][0].emplace_back(y),t[y][1].emplace_back(x),++in[y]; //拓扑排序 for(int i=1;i<=n;++i) if(!in[i])q.push(i); while(!q.empty()){ x=q.front();q.pop();ord[++len]=x,id[x]=len; for(auto i:t[x][0]) if(!(--in[i]))q.push(i); } solve(1,n,0); cout<<a<<" "<<ans<<'\n'; return 0; }当然,这种做法也能做带权情况。
- 1
信息
- ID
- 5497
- 时间
- 3000ms
- 内存
- 228MiB
- 难度
- 10
- 标签
- 递交数
- 8
- 已通过
- 3
- 上传者