【BZOJ】1576 [Usaco2009 Jan]安全路经Travel
【算法】最短路树+(树链剖分+线段树)||最短路树+并查集
【题解】
两种方法的思想是一样的,首先题目限制了最短路树唯一。
那么建出最短路树后,就是询问对于每个点断掉父边后重新找路径的最小值,其它路径只能是这个点和其子树节点通过非树边到达非子树节点。
这样考虑很难统计,换个角度考虑每条非树边的影响。
一条非树边连接两个端点u,v,它们会有一个LCA,那么这条非树边就可以影响u~LCA和v~LCA两条链上的点。
这样依然不方便统计,因为两条链上每个点的影响各不相同,所以使用差分的思想。
定义一条非树边对两条链上的点的贡献为g[i]=dis[u]+dis[v]+e[i].w,那么对于两条链上的每个点就是ans[x]=min(ans[x],g[i]-dis[x]),因为dis[x]是每个点自身属性,那么就可以统一地对两条链上上的点赋值g[i]。
现在,我们可以明确每条非树边对特定的两条边的贡献,那么显然可以用树链剖分+线段树对两条链上的点进行【区间最小值覆盖+单点查询最小值】,这一操作可以用标记永久化实现。
考虑另一种写法,如果我们把非树边按贡献排序,那么贡献小的覆盖之后,贡献大的就不可能影响到这些被覆盖过的点了,那么可以将覆盖过的点用并查集合并为一个点,遇到直接跳。
复杂度O(m log n)。
<并查集>
#include<cstdio> #include<algorithm> #include<queue> #include<cctype> #include<cstring> using namespace std; const int maxn=400010,inf=0x3f3f3f3f; struct edge{int u,v,w,from;}e[maxn*2]; int n,m,cnt,ans[maxn],first[maxn],tot,fa[maxn],f[maxn],deep[maxn],dis[maxn],d[maxn],c[maxn]; int read() { char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } void insert(int u,int v,int w) {tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} struct cyc{ int x,d; bool operator < (const cyc &a)const{ return d>a.d; } }; priority_queue<cyc>q; void dijkstra(){ memset(d,0x3f,sizeof(d)); deep[1]=d[1]=0;q.push((cyc){1,0}); while(!q.empty()){ cyc x=q.top();q.pop(); if(x.d!=d[x.x])continue; for(int i=first[x.x];i;i=e[i].from)if(d[e[i].v]>d[x.x]+e[i].w){ d[e[i].v]=d[x.x]+e[i].w; deep[e[i].v]=deep[x.x]+1; f[e[i].v]=x.x; q.push((cyc){e[i].v,d[e[i].v]}); } } } struct cyc2{int u,v,num;}b[maxn]; bool cmp(cyc2 a,cyc2 b){return a.num<b.num;} int main(){ n=read();m=read(); int u,v,w; for(int i=1;i<=m;i++){ u=read();v=read();w=read(); insert(u,v,w);insert(v,u,w); } dijkstra(); for(int i=1;i<=tot;i+=2){ if(deep[e[i].u]<deep[e[i].v])swap(e[i].u,e[i].v); if(d[e[i].u]!=d[e[i].v]+e[i].w)b[++cnt]=(cyc2){e[i].u,e[i].v,d[e[i].u]+d[e[i].v]+e[i].w}; } sort(b+1,b+cnt+1,cmp); for(int i=1;i<=n;i++)fa[i]=i; f[1]=1;//初始父亲 for(int i=1;i<=cnt;i++){ int x=find(b[i].u),y=find(b[i].v); while(x!=y){ if(deep[x]<deep[y])swap(x,y); if(!ans[x])ans[x]=b[i].num; x=fa[x]=find(f[x]); } } for(int i=2;i<=n;i++)if(!ans[i])printf("-1\n");else printf("%d\n",ans[i]-d[i]); return 0; }
补充说明:【排序+并查集】是一种套路,处理MST的著名算法kruskal就是使用这种思想。这种做法要求无后效性,将价值最大的边纳入然后并成一个点继续处理,从而保证最优性。
<树链剖分+线段树>
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int inf=0x3f3f3f3f,maxn=100010,maxm=200010; struct edge{int u,from,v,w;}e[maxm*3]; struct tree{int l,r,tag;}t[maxn*3]; int n,m,tot=0,first[maxn],q[100010],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=0; bool mark[maxm*3],vis[maxn]; void insert(int u,int v,int w) {tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} void spfa() { memset(d,0x3f,sizeof(d)); memset(mark,0,sizeof(mark)); memset(vis,0,sizeof(vis)); int head=0,tail=1;q[0]=1;vis[1]=1;d[1]=0; while(head!=tail) { int x=q[head++];if(head>100000)head=0; for(int i=first[x];i;i=e[i].from) if(d[e[i].v]>d[x]+e[i].w) { int y=e[i].v; d[y]=d[x]+e[i].w; fa[y]=x; mark[te[y]]=0; te[y]=i; mark[i]=1; if(!vis[y]){q[tail++]=y;if(tail>100000)tail=0;} vis[y]=1; } vis[x]=0; } // for(int i=1;i<=n;i++)printf("fa[%d]=%d d[%d]=%d\n",i,fa[i],i,d[i]); // for(int i=1;i<=tot;i++)printf("[%d]%d %d %d\n",i,e[i].u,e[i].v,mark[i]); } void build(int k,int l,int r) { t[k].l=l;t[k].r=r;t[k].tag=inf; if(l==r)return; int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); } void dfs1(int x) { size[x]=1; for(int i=first[x];i;i=e[i].from) if(mark[i]) { int y=e[i].v; deep[y]=deep[x]+1; dfs1(y); size[x]+=size[y]; } } void dfs2(int x,int tp) { int k=0; top[x]=tp; pos[x]=++dfsnum; for(int i=first[x];i;i=e[i].from) if(mark[i]&&size[e[i].v]>size[k])k=e[i].v; if(k==0)return; dfs2(k,tp); for(int i=first[x];i;i=e[i].from) if(mark[i]&&e[i].v!=k)dfs2(e[i].v,e[i].v); } void seg_insert(int k,int l,int r,int x) { if(l<=t[k].l&&t[k].r<=r) { t[k].tag=min(t[k].tag,x); return; } else { int mid=(t[k].l+t[k].r)>>1; if(l<=mid)seg_insert(k<<1,l,r,x); if(r>mid)seg_insert(k<<1|1,l,r,x); } } int seg_ask(int k,int x) { if(t[k].l==t[k].r)return t[k].tag; int mid=(t[k].l+t[k].r)>>1; if(x<=mid)return min(t[k].tag,seg_ask(k<<1,x)); else return min(t[k].tag,seg_ask(k<<1|1,x)); } void solve_ins(int x,int y,int w) { while(top[x]!=top[y]) { if(deep[top[x]]<deep[top[y]])swap(x,y); seg_insert(1,pos[top[x]],pos[x],w); x=fa[top[x]]; } if(pos[x]>pos[y])swap(x,y); if(pos[x]<pos[y])seg_insert(1,pos[x]+1,pos[y],w); } int main() { scanf("%d%d",&n,&m); int u,v,w; for(int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); insert(u,v,w);insert(v,u,w); } spfa(); build(1,1,n);dfs1(1);dfs2(1,1);//printf("sldf\n"); for(int i=1;i<=m;i++) if(!mark[i*2-1]&&!mark[i*2])solve_ins(e[i*2].u,e[i*2].v,d[e[i*2].u]+d[e[i*2].v]+e[i*2].w); // printf("asfjld\n"); for(int i=2;i<=n;i++) { int ans=seg_ask(1,pos[i]); if(ans>inf-100)ans=d[i]-1; printf("%d\n",ans-d[i]); } return 0; }
#include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int inf=0x3f3f3f3f,maxn=100010,maxm=200010; struct edge{int u,from,v,w;}e[maxm*3]; struct tree{int l,r,tag;}t[maxn*3]; int n,m,tot=0,first[maxn],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=0; bool mark[maxm*3],vis[maxn]; void insert(int u,int v,int w) {tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} struct Node{int x,d;}cyc; priority_queue<Node>q; bool operator <(Node a,Node b) {return a.d>b.d;} void dijkstra() { memset(d,0x3f,sizeof(d)); memset(mark,0,sizeof(mark)); d[1]=0;cyc.d=0;cyc.x=1;q.push(cyc); while(!q.empty()) { cyc=q.top();q.pop(); int x=cyc.x; if(cyc.d!=d[x])continue; for(int i=first[x];i;i=e[i].from) if(d[e[i].v]>d[x]+e[i].w) { int y=e[i].v; d[y]=d[x]+e[i].w; cyc.x=y;cyc.d=d[y];q.push(cyc); mark[te[y]]=0; te[y]=i;mark[i]=1; fa[y]=x; } } // for(int i=1;i<=n;i++)printf("fa[%d]=%d d[%d]=%d\n",i,fa[i],i,d[i]); // for(int i=1;i<=tot;i++)printf("[%d]%d %d %d\n",i,e[i].u,e[i].v,mark[i]); } void build(int k,int l,int r) { t[k].l=l;t[k].r=r;t[k].tag=inf; if(l==r)return; int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); } void dfs1(int x) { size[x]=1; for(int i=first[x];i;i=e[i].from) if(mark[i]) { int y=e[i].v; deep[y]=deep[x]+1; dfs1(y); size[x]+=size[y]; } } void dfs2(int x,int tp) { int k=0; top[x]=tp; pos[x]=++dfsnum; for(int i=first[x];i;i=e[i].from) if(mark[i]&&size[e[i].v]>size[k])k=e[i].v; if(k==0)return; dfs2(k,tp); for(int i=first[x];i;i=e[i].from) if(mark[i]&&e[i].v!=k)dfs2(e[i].v,e[i].v); } void seg_insert(int k,int l,int r,int x) { if(l<=t[k].l&&t[k].r<=r) { t[k].tag=min(t[k].tag,x); return; } else { int mid=(t[k].l+t[k].r)>>1; if(l<=mid)seg_insert(k<<1,l,r,x); if(r>mid)seg_insert(k<<1|1,l,r,x); } } int seg_ask(int k,int x) { if(t[k].l==t[k].r)return t[k].tag; int mid=(t[k].l+t[k].r)>>1; if(x<=mid)return min(t[k].tag,seg_ask(k<<1,x)); else return min(t[k].tag,seg_ask(k<<1|1,x)); } void solve_ins(int x,int y,int w) { while(top[x]!=top[y]) { if(deep[top[x]]<deep[top[y]])swap(x,y); seg_insert(1,pos[top[x]],pos[x],w); x=fa[top[x]]; } if(pos[x]>pos[y])swap(x,y); if(pos[x]<pos[y])seg_insert(1,pos[x]+1,pos[y],w); } int main() { scanf("%d%d",&n,&m); int u,v,w; for(int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); insert(u,v,w);insert(v,u,w); } dijkstra(); build(1,1,n);dfs1(1);dfs2(1,1);//printf("sldf\n"); for(int i=1;i<=m;i++) if(!mark[i*2-1]&&!mark[i*2])solve_ins(e[i*2].u,e[i*2].v,d[e[i*2].u]+d[e[i*2].v]+e[i*2].w); // printf("asfjld\n"); for(int i=2;i<=n;i++) { int ans=seg_ask(1,pos[i]); if(ans>inf-100)ans=d[i]-1; printf("%d\n",ans-d[i]); } return 0; }
事实证明,Dijkstra比SPFA稳得多,虽然也可能是故意卡的,但终归卡不了Dijkstra,因为本来理论上界就小。
Dijkstra+Heap 2.5s
SPFA+SLF 10s
SPFA TLE