【算法】最短路树+(树链剖分+线段树)||最短路树+并查集

【题解】

两种方法的思想是一样的,首先题目限制了最短路树唯一。

那么建出最短路树后,就是询问对于每个点断掉父边后重新找路径的最小值,其它路径只能是这个点和其子树节点通过非树边到达非子树节点。

这样考虑很难统计,换个角度考虑每条非树边的影响。

一条非树边连接两个端点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=,inf=0x3f3f3f3f;
struct edge{int u,v,w,from;}e[maxn*];
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=,t=;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}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[]=d[]=;q.push((cyc){,});
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]+;
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=;i<=m;i++){
u=read();v=read();w=read();
insert(u,v,w);insert(v,u,w);
}
dijkstra();
for(int i=;i<=tot;i+=){
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+,b+cnt+,cmp);
for(int i=;i<=n;i++)fa[i]=i;
f[]=;//初始父亲
for(int i=;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=;i<=n;i++)if(!ans[i])printf("-1\n");else printf("%d\n",ans[i]-d[i]);
return ;
}

补充说明:【排序+并查集】是一种套路,处理MST的著名算法kruskal就是使用这种思想。这种做法要求无后效性,将价值最大的边纳入然后并成一个点继续处理,从而保证最优性。

<树链剖分+线段树>

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f,maxn=,maxm=;
struct edge{int u,from,v,w;}e[maxm*];
struct tree{int l,r,tag;}t[maxn*];
int n,m,tot=,first[maxn],q[],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=;
bool mark[maxm*],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,,sizeof(mark));
memset(vis,,sizeof(vis));
int head=,tail=;q[]=;vis[]=;d[]=;
while(head!=tail)
{
int x=q[head++];if(head>)head=;
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]]=;
te[y]=i;
mark[i]=;
if(!vis[y]){q[tail++]=y;if(tail>)tail=;}
vis[y]=;
}
vis[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)>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
void dfs1(int x)
{
size[x]=;
for(int i=first[x];i;i=e[i].from)
if(mark[i])
{
int y=e[i].v;
deep[y]=deep[x]+;
dfs1(y);
size[x]+=size[y];
}
}
void dfs2(int x,int tp)
{
int k=;
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==)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)>>;
if(l<=mid)seg_insert(k<<,l,r,x);
if(r>mid)seg_insert(k<<|,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)>>;
if(x<=mid)return min(t[k].tag,seg_ask(k<<,x));
else return min(t[k].tag,seg_ask(k<<|,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(,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(,pos[x]+,pos[y],w);
}
int main()
{
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);insert(v,u,w);
}
spfa();
build(,,n);dfs1();dfs2(,);//printf("sldf\n");
for(int i=;i<=m;i++)
if(!mark[i*-]&&!mark[i*])solve_ins(e[i*].u,e[i*].v,d[e[i*].u]+d[e[i*].v]+e[i*].w);
// printf("asfjld\n");
for(int i=;i<=n;i++)
{
int ans=seg_ask(,pos[i]);
if(ans>inf-)ans=d[i]-;
printf("%d\n",ans-d[i]);
}
return ;
}

SPFA+链剖+线段树

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f,maxn=,maxm=;
struct edge{int u,from,v,w;}e[maxm*];
struct tree{int l,r,tag;}t[maxn*];
int n,m,tot=,first[maxn],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=;
bool mark[maxm*],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,,sizeof(mark));
d[]=;cyc.d=;cyc.x=;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]]=;
te[y]=i;mark[i]=;
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)>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
void dfs1(int x)
{
size[x]=;
for(int i=first[x];i;i=e[i].from)
if(mark[i])
{
int y=e[i].v;
deep[y]=deep[x]+;
dfs1(y);
size[x]+=size[y];
}
}
void dfs2(int x,int tp)
{
int k=;
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==)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)>>;
if(l<=mid)seg_insert(k<<,l,r,x);
if(r>mid)seg_insert(k<<|,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)>>;
if(x<=mid)return min(t[k].tag,seg_ask(k<<,x));
else return min(t[k].tag,seg_ask(k<<|,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(,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(,pos[x]+,pos[y],w);
}
int main()
{
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);insert(v,u,w);
}
dijkstra();
build(,,n);dfs1();dfs2(,);//printf("sldf\n");
for(int i=;i<=m;i++)
if(!mark[i*-]&&!mark[i*])solve_ins(e[i*].u,e[i*].v,d[e[i*].u]+d[e[i*].v]+e[i*].w);
// printf("asfjld\n");
for(int i=;i<=n;i++)
{
int ans=seg_ask(,pos[i]);
if(ans>inf-)ans=d[i]-;
printf("%d\n",ans-d[i]);
}
return ;
}

Dijkstra+链剖+线段树

事实证明,Dijkstra比SPFA稳得多,虽然也可能是故意卡的,但终归卡不了Dijkstra,因为本来理论上界就小。

Dijkstra+Heap 2.5s

SPFA+SLF 10s

SPFA TLE

【BZOJ】1576 [Usaco2009 Jan]安全路经Travel的更多相关文章

  1. bzoj 1576: [Usaco2009 Jan]安全路经Travel 树链剖分

    1576: [Usaco2009 Jan]安全路经Travel Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 665  Solved: 227[Sub ...

  2. [BZOJ 1576] [Usaco2009 Jan] 安全路经Travel 【树链剖分】

    题目链接: BZOJ - 1576 题目分析 首先Orz Hzwer的题解. 先使用 dijikstra 求出最短路径树. 那么对于一条不在最短路径树上的边 (u -> v, w) 我们可以先沿 ...

  3. bzoj 1576 [Usaco2009 Jan]安全路经Travel(树链剖分,线段树)

    [题意] 给定一个无向图,找到1-i所有的次短路经,要求与最短路径的最后一条边不重叠. [思路] 首先用dijkstra算法构造以1为根的最短路树. 将一条无向边看作两条有向边,考察一条不在最短路树上 ...

  4. BZOJ.1576.[Usaco2009 Jan]安全路经Travel(树形DP 并查集)

    题目链接 BZOJ 洛谷 先求最短路树.考虑每一条非树边(u,v,len),设w=LCA(u,v),这条边会对w->v上的点x(x!=w)有dis[u]+dis[v]-dis[x]+len的距离 ...

  5. bzoj 1576: [Usaco2009 Jan]安全路经Travel——并查集+dijkstra

    Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第i行包含一个数 ...

  6. bzoj 1576: [Usaco2009 Jan]安全路经Travel【spfa+树链剖分+线段树】

    这几天写USACO水题脑子锈住了--上来就贪心,一交就WA 事实上这个是一个叫最短路树的东西,因为能保证只有一条最短路,所以所有最短路合起来是一棵以1为根的树,并且在这棵树上,每个点被精灵占据的路是它 ...

  7. BZOJ 1576: [Usaco2009 Jan]安全路经Travel

    日常自闭半小时后看题解,太弱了qwq. 感觉这道题还是比较难的,解法十分巧妙,不容易想到. 首先题目说了起点到每个点的最短路都是唯一的,那么对这个图求最短路图必定是一棵树,而且这棵树是唯一的. 那么我 ...

  8. 【BZOJ1576】[Usaco2009 Jan]安全路经Travel 最短路+并查集

    [BZOJ1576][Usaco2009 Jan]安全路经Travel Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, ...

  9. 【思维题 并查集 图论】bzoj1576: [Usaco2009 Jan]安全路经Travel

    有趣的思考题 Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第 ...

随机推荐

  1. Alpha-7

    前言 失心疯病源7 团队代码管理github 站立会议 队名:PMS 530雨勤(组长) 今天完成了那些任务 18:30~20:30 通过统计法来得出人车团块的区别和鉴别方法,然而效果并不显著 代码签 ...

  2. 0422 寻找数学口袋精灵BUG

    首先要部署这个app项目就是第一步: 一.前提下载并安装JDK 在线图解:手把手教你安装JDK      http://www.lvtao.net/server/windows-setup-jdk.h ...

  3. PAT L1-034 点赞

    https://pintia.cn/problem-sets/994805046380707840/problems/994805098188750848 微博上有个“点赞”功能,你可以为你喜欢的博文 ...

  4. 【C】多线程编程笔记

    1. pthread_create(pthread类型指针变量 ,NULL ,函数 ,函数参数[多个参数用结构体传]) 2. pthread_join(pthread类型指针变量, 返回一般为null ...

  5. 淘宝免费ip地址查询导致服务堵死的坑

    1.业务中因为想根据用户ip来做一些友好的提示,所以在网上找了个免费的ip查询地址 http://ip.taobao.com/service/getIpInfo.php?ip= 虽然说淘宝的这个地址会 ...

  6. web移动开发最佳实践之js篇

    一.js概述 js即JavaScript,是被设计用来验证表单.检测浏览器.创建cookies.改进设计以及更多应用的网络脚本语言,它非常容易使用.在web应用中,它是主要的编程语言,主要用途是进行各 ...

  7. jumpserver的安装部署

    废话不说直接安装 1:安装数据库 这里是提前安装,也可以不安装,在安装jumpserver主程序的时候,他会询问你是否安装 yum -y install ncurses-devel cmake ech ...

  8. 第138天:Web前端面试题总结(编程)

    1.如何让一个盒子水平垂直居中 //已知宽高 <div class="div1"></div> <style> .div1{ width:400 ...

  9. 转:机器学习 规则化和模型选择(Regularization and model selection)

    规则化和模型选择(Regularization and model selection) 转:http://www.cnblogs.com/jerrylead/archive/2011/03/27/1 ...

  10. yii2框架-yii2局部关闭(开启)csrf的验证

    (1)全局使用,我们直接在配置文件中设置enableCookieValidation为true request => [ 'enableCookieValidation' => true, ...