定義構建一棵樹,使得任意不屬於根的節點x,dis(root,x)=原圖走到x的最短路。
構建方法:就是在跑dijkstra時同時維護每個點是哪個點哪條邊更新的,這個點這條邊就是它在最短路樹上的父親/到父親的邊
例題:
T1.bzoj3694 最短路
題意:給定了最短路樹要求不經過樹上到i點最後一條邊的到i點最短路。
顯然到一個點走法一定是經過一條非樹邊到達的,那麼考慮每一條非樹邊會產生怎樣的貢獻:假設一條非樹邊(u,v)權值w,那麼可以發現對於任意u,v路徑上除lca外的點x,假設x在u到lca上,它的ans可以用dis(v)+w+dis(u)-dis(x)來更新,帶x的項最後考慮那麼就是鏈取min顯然大力樹剖即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+100;
const ll inf=1e18;
template<class T>
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
void Mn(ll &x,ll y)
{x=min(x,y);}
int bg[N],ed[N],val[N],num=0;
int n,m,hd[N],to[N],w[N],nxt[N],tot=-1;
int fa[N],son[N],dfn[N],top[N],dep[N],sz[N],tim=0;
ll dis[N],seg[N<<2],laz[N<<2];
void add(int x,int y,int z)
{
nxt[++tot]=hd[x],to[tot]=y,w[tot]=z,hd[x]=tot;
nxt[++tot]=hd[y],to[tot]=x,w[tot]=z,hd[y]=tot;
}
void dfs0(int x,int f)
{
fa[x]=f,sz[x]=1,son[x]=0;
for(int i=hd[x],v;~i;i=nxt[i])
{
v=to[i];
if(v==f)continue;
dep[v]=dep[x]+1,dis[v]=dis[x]+w[i],dfs0(v,x),sz[x]+=sz[v];
if(sz[v]>sz[son[x]])son[x]=v;
}
}
void dfs1(int x,int tp)
{
dfn[x]=++tim,top[x]=tp;
if(son[x])
{
dfs1(son[x],tp);
for(int i=hd[x],v;~i;i=nxt[i])
{
v=to[i];
if(v==fa[x]||v==son[x])continue;
dfs1(v,v);
}
}
}
int get_lca(int x,int y)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
x=fa[fx],fx=top[x];
}
return dep[x]<dep[y]?x:y;
}
void push_down(int k)
{
if(laz[k]<inf)
{
Mn(seg[k<<1],laz[k]),Mn(laz[k<<1],laz[k]);
Mn(seg[k<<1|1],laz[k]),Mn(laz[k<<1|1],laz[k]);
laz[k]=inf;
}
}
void upd(int L,int R,int l,int r,int k,ll x)
{
//cerr<<l<<' '<<r<<' '<<k<<'\n';
if(L<=l&&r<=R)
{
Mn(seg[k],x);
Mn(laz[k],x);
return;
}
int mid=(l+r)>>1;
push_down(k);
if(L<=mid)upd(L,R,l,mid,k<<1,x);
if(R>mid)upd(L,R,mid+1,r,k<<1|1,x);
}
void cg(int x,int y,ll z)
{
int fx=top[x],fy=top[y];
//cerr<<fx<<' '<<fy<<'\n';
while(fx!=fy)
{
//cerr<<n<<'\n';
upd(dfn[fx],dfn[x],1,n,1,z);
x=fa[fx],fx=top[x];
}
if(x==y)return;
upd(dfn[y]+1,dfn[x],1,n,1,z);
}
ll qry(int to,int l,int r,int k)
{
if(l==r)return seg[k];
int mid=(l+r)>>1;
push_down(k);
if(to<=mid)return qry(to,l,mid,k<<1);
else return qry(to,mid+1,r,k<<1|1);
}
int main()
{
int x,y,z,op;
rd(n),rd(m);
memset(hd,-1,sizeof hd);
for(int i=1;i<=m;i++)
{
rd(x),rd(y),rd(z),rd(op);
if(op==1)add(x,y,z);
else bg[++num]=x,ed[num]=y,val[num]=z;
}
dep[1]=1,dfs0(1,0),dfs1(1,1);
memset(seg,63,sizeof seg);
memset(laz,63,sizeof laz);
for(int i=1,u,v,lca;i<=num;i++)
{
u=bg[i],v=ed[i];
if(dep[u]<dep[v])swap(u,v);
lca=get_lca(u,v);
//cerr<<u<<' '<<v<<' '<<lca<<'\n';
if(lca==v)cg(u,v,dis[u]+dis[v]+val[i]);
else cg(u,lca,dis[u]+dis[v]+val[i]),cg(v,lca,dis[u]+dis[v]+val[i]);
}
ll res;
for(int i=2;i<=n;i++)
{
res=qry(dfn[i],1,n,1);
if(res>=inf)printf("-1 ");
else printf("%lld ",res-dis[i]);
}
}
T2.cf 1005F
求建最短路樹的方案
並不用真的把最短路樹建出來,因爲邊權都是1所以很好做,bfs求出每個點的dis,再對每個點遍歷所有連到的點,如果dis是當前減一那麼必然可以成爲當前點前驅,扔進一個vector,那麼總方案就是所有點vector size的乘積,輸出方案則dfs一下即可。
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define ll long long
using namespace std;
const int N=2e5+100;
const ll inf=1e18;
template<class T>
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
void Mn(ll &x,ll y)
{x=min(x,y);}
int n,m,k,dis[N],can;
bool vis[N];
vector<pii>mp[N],fr[N];
void P()
{
for(int i=1;i<=m;i++)
vis[i]?putchar('1'):putchar('0');
puts("");
}
void dfs(int x)
{
if(x>n)
{
P();
--can;
if(can==0)exit(0);
}
for(int i=0,v,wh;i<fr[x].size();i++)
{
v=fr[x][i].fi,wh=fr[x][i].sc;
vis[wh]=1,dfs(x+1),vis[wh]=0;
}
}
int main()
{
rd(n),rd(m),rd(k);
for(int i=1,u,v;i<=m;i++)
{
rd(u),rd(v);
mp[u].push_back(pii(v,i));
mp[v].push_back(pii(u,i));
}
int nw;
queue<int>q;
memset(dis,-1,sizeof dis);
dis[1]=0,q.push(1);
while(!q.empty())
{
nw=q.front(),q.pop();
for(int i=0,v;i<mp[nw].size();i++)
{
v=mp[nw][i].fi;
if(dis[v]==-1)dis[v]=dis[nw]+1,q.push(v);
}
}
for(int i=2;i<=n;i++)
{
for(int j=0,v;j<mp[i].size();j++)
{
v=mp[i][j].fi;
if(dis[v]+1==dis[i])fr[i].push_back(mp[i][j]);
}
}
can=1;
for(int i=2;i<=n;i++)
{
can*=fr[i].size();
can=min(can,k);
}
printf("%d\n",can);
dfs(2);
}
T3.cf 1076D. Edge Deletion
顯然就是從最短路樹底層往上一個個刪除。
#include<bits/stdc++.h>
#define pii pair<ll,int>
#define fi first
#define sc second
#define ll long long
using namespace std;
const int N=3e5+100;
const ll inf=1e18;
template<class T>
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
void Mn(ll &x,ll y)
{x=min(x,y);}
int n,m,k,hd[N],nxt[N*2],to[N*2],cost[N*2],idx[N*2],bf[N],tax[N],tot=-1;
ll dis[N];
bool kep[N];
void add(int x,int y,int z,int id)
{
nxt[++tot]=hd[x],idx[tot]=id,to[tot]=y,cost[tot]=z,hd[x]=tot;
nxt[++tot]=hd[y],idx[tot]=id,to[tot]=x,cost[tot]=z,hd[y]=tot;
}
void dij()
{
priority_queue<pii,vector<pii>,greater<pii> >pq;
int nw;ll d;
memset(dis,63,sizeof dis);
dis[1]=0,pq.push(pii(0,1));
while(!pq.empty())
{
nw=pq.top().sc,d=pq.top().fi,pq.pop();
if(dis[nw]<d)continue;
for(int i=hd[nw],v;~i;i=nxt[i])
{
v=to[i];
if(dis[v]>d+cost[i])
{
dis[v]=d+cost[i];
bf[v]=idx[i];
pq.push(pii(dis[v],v));
}
}
}
}
bool cmp(int x,int y)
{return dis[x]>dis[y];}
int main()
{
rd(n),rd(m),rd(k);
memset(hd,-1,sizeof hd);
for(int i=1,u,v,w;i<=m;i++)
{
//cerr<<i<<'\n';
rd(u),rd(v),rd(w);
add(u,v,w,i);
}
//cerr<<"!!"<<'\n';
dij();
for(int i=1;i<n;i++)
tax[i]=i+1,kep[bf[i+1]]=1;
sort(tax+1,tax+n,cmp);
int has=n-1,ps=1;
while(has>k)
{
kep[bf[tax[ps++]]]=0;
--has;
}
printf("%d\n",has);
for(int i=1;i<=m;i++)
if(kep[i])printf("%d ",i);
}