P2483-[模板]k短路/[SDOI2010]魔法猪学院【主席树,堆】
正题
题目链接:https://www.luogu.com.cn/problem/P2483
题目大意
给出一个\(n\)个点\(m\)条边的一张带权有向图,求一个最大的\(k\)使得\(1\sim n\)的前\(k\)短路径长度和不超过\(E\)。
\(2\leq n\leq 5000,1\leq m\leq 2\times 10^5,1\leq E\leq 10^7\)
解题思路
我们先把从\(n\)出发的一棵反向最短路树跑出来,注意这里的最短路树是真的一棵树,我们从\(DAG\)中随便提一些边出来构成一棵树,以\(n\)为根。
然后考虑我们将其他的路径中不在树上的路径拿出来,那么这些边肯定满足前一条边的终点\(t\)肯定在后一条边起点\(s\)的子树中。
如果我们能确定这样一个有序的边集就相当于确定了这样一条唯一的路径。
然后考虑一条边\((x,y,w)\)的边权视为\(dis_y-dis_x+w\),这样最终的路径长度就是\(dis_1\)加上这些边的权值和。
然后考虑怎么去扩展我们的方案,假设我们现在的边集\(E\)的倒数第一个和最后一个分别是\((x',y',w'),(x,y,w)\)
那么我们有两种扩展方式:
- 在最后加一条边,这条边的起点一点得是\(y\)或\(y\)的祖先。
- 替换最后一条边,这条边必须是起点是\(y'\)或其祖先中恰好比当前这条边权大的边。
因为有重复边权,我们还是给每条边一个具体的顺序。
然后用堆维护最后两条边的状态就好了,主席树维护每个点其祖先的所有出边的一棵权值线段树。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=2e5+10,M=N<<5;
const double eps=1e-8;
struct edge{
int x,y;
bool ban;
double w;
}e[N];
struct node{
int to,next,w;
}a[N];
int n,m,tot,ans,ls[N],ban[N],fa[N],rt[N];
double f[N],k;bool v[N];map<int,int> u[N];
vector<int> T[N],ot[N];
priority_queue<pair<double,int> >_q;
priority_queue<pair<double,pair<int,int> > >q;
struct SegTree{
int cnt,w[M],ls[M],rs[M];
int Change(int x,int L,int R,int pos){
int p=++cnt;w[p]=w[x]+1;
if(L==R)return p;int mid=(L+R)>>1;
if(pos<=mid)ls[p]=Change(ls[x],L,mid,pos),rs[p]=rs[x];
else rs[p]=Change(rs[x],mid+1,R,pos),ls[p]=ls[x];
return p;
}
int Ask(int x,int L,int R,int k){
if(k>R||!w[x])return 0;
if(L==R)return L;int mid=(L+R)>>1;
if(k>mid)return Ask(rs[x],mid+1,R,k);
int ans=Ask(ls[x],L,mid,k);
if(!ans)ans=Ask(rs[x],mid+1,R,k);
return ans;
}
}S;
void addl(int x,int y,int w){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;a[tot].w=w;
return;
}
void dij(){
for(int i=1;i<n;i++)f[i]=1e100;
_q.push(mp(0,n));
while(!_q.empty()){
int x=_q.top().second;_q.pop();
if(v[x])continue;v[x]=1;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(f[x]+e[a[i].w].w<f[y]){
f[y]=f[x]+e[a[i].w].w;
ban[y]=a[i].w;fa[y]=x;
_q.push(mp(-f[y],y));
}
}
}
return;
}
bool cmp(edge x,edge y)
{return x.w<y.w;}
void dfs(int x){
for(int i=0;i<ot[x].size();i++)
rt[x]=S.Change(rt[x],1,m,ot[x][i]);
for(int i=0;i<T[x].size();i++)
rt[T[x][i]]=rt[x],dfs(T[x][i]);
return;
}
void solve(){
int x=S.Ask(rt[1],1,m,0);
q.push(mp(-f[1]-e[x].w,mp(1,x)));
while(!q.empty()){
double w=-q.top().first;
int x=q.top().second.first;
int y=q.top().second.second;
q.pop();
if(k+eps<w){ans++,ans--;return;}
k-=w;ans++;
int p=S.Ask(rt[e[y].y],1,m,1);
if(p)q.push(mp(-w-e[p].w,mp(e[y].y,p)));
p=S.Ask(rt[x],1,m,y+1);
if(p)q.push(mp(-w+e[y].w-e[p].w,mp(x,p)));
}
}
int main()
{
// freopen("P2483_3.in","r",stdin);
scanf("%d%d%lf",&n,&m,&k);
for(int i=1;i<=m;i++){
scanf("%d%d%lf",&e[i].x,&e[i].y,&e[i].w);
addl(e[i].y,e[i].x,i);
}
dij();
if(k<f[1])return puts("0")&0;k-=f[1];ans++;
for(int i=1;i<n;i++)e[ban[i]].ban=1;
for(int i=1;i<n;i++)T[fa[i]].push_back(i);
for(int i=1;i<=m;i++)
if(!e[i].ban&&e[i].x!=n)
e[i].w=f[e[i].y]+e[i].w-f[e[i].x];
else swap(e[i],e[m]),m--,i--;
sort(e+1,e+1+m,cmp);
for(int i=1;i<=m;i++)ot[e[i].x].push_back(i);
dfs(n);solve();
printf("%d\n",ans);
return 0;
}