【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=,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的更多相关文章
- bzoj 1576: [Usaco2009 Jan]安全路经Travel 树链剖分
1576: [Usaco2009 Jan]安全路经Travel Time Limit: 10 Sec Memory Limit: 64 MB Submit: 665 Solved: 227[Sub ...
- [BZOJ 1576] [Usaco2009 Jan] 安全路经Travel 【树链剖分】
题目链接: BZOJ - 1576 题目分析 首先Orz Hzwer的题解. 先使用 dijikstra 求出最短路径树. 那么对于一条不在最短路径树上的边 (u -> v, w) 我们可以先沿 ...
- bzoj 1576 [Usaco2009 Jan]安全路经Travel(树链剖分,线段树)
[题意] 给定一个无向图,找到1-i所有的次短路经,要求与最短路径的最后一条边不重叠. [思路] 首先用dijkstra算法构造以1为根的最短路树. 将一条无向边看作两条有向边,考察一条不在最短路树上 ...
- 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的距离 ...
- bzoj 1576: [Usaco2009 Jan]安全路经Travel——并查集+dijkstra
Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第i行包含一个数 ...
- bzoj 1576: [Usaco2009 Jan]安全路经Travel【spfa+树链剖分+线段树】
这几天写USACO水题脑子锈住了--上来就贪心,一交就WA 事实上这个是一个叫最短路树的东西,因为能保证只有一条最短路,所以所有最短路合起来是一棵以1为根的树,并且在这棵树上,每个点被精灵占据的路是它 ...
- BZOJ 1576: [Usaco2009 Jan]安全路经Travel
日常自闭半小时后看题解,太弱了qwq. 感觉这道题还是比较难的,解法十分巧妙,不容易想到. 首先题目说了起点到每个点的最短路都是唯一的,那么对这个图求最短路图必定是一棵树,而且这棵树是唯一的. 那么我 ...
- 【BZOJ1576】[Usaco2009 Jan]安全路经Travel 最短路+并查集
[BZOJ1576][Usaco2009 Jan]安全路经Travel Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, ...
- 【思维题 并查集 图论】bzoj1576: [Usaco2009 Jan]安全路经Travel
有趣的思考题 Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第 ...
随机推荐
- Lucene 常用名词解析
索引的创建:IndexWriter: 用于创建索引Directory: 这个可以用来定义我们的索引是存放在内存中还是在硬盘上Analyzer: 分词器 有几种()这个地方需要好好解释下Document ...
- 三层神经网络自编码算法推导和MATLAB实现 (转载)
转载自:http://www.cnblogs.com/tornadomeet/archive/2013/03/20/2970724.html 前言: 现在来进入sparse autoencoder的一 ...
- 安装php先行库
libmcrypt libconv mhash ./configure --prefix=/usr/local mcrypt 安装完成后在当前目录还要 /sbin/ldconfig ./config ...
- artdialog对话框 三种样式 网址:http://www.planeart.cn/demo/artDialog/_doc/labs.html
摇头效果 类似与wordpress登录失败后登录框可爱的左右晃动效果 // 2011-07-17 更新 artDialog.fn.shake = function (){ var style = th ...
- javascript 排序
// 插入排序 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置.(如果待插入的元素与有序 ...
- javascript之彻底理解this
彻底理解this,需要彻底理解函数 函数是复杂类型,存储在堆中. 函数是独立的, 对象中的方法只是对象中有个函数的引用 函数被调用时,调用者会像被调用者提供个上下文环境, 这个环境就是this 构造 ...
- 网络流量统计using ADB
/proc/net/xt_qtaguid/stats 基本覆盖目前所有机型且统计流量全面 adb shell cat /proc/net/xt_qtaguid/stats | grep (uid#) ...
- 【bzoj4321】queue2 dp
题目描述 n 个沙茶,被编号 1~n.排完队之后,每个沙茶希望,自己的相邻的两人只要无一个人的编号和自己的编号相差为 1(+1 或-1)就行: 现在想知道,存在多少方案满足沙茶们如此不苛刻的条件. ...
- BGP与BGP机房 国内网络运营商的主流网关解决方案
边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议. BGP 是唯一一个用来处理像因特网大小的网络的协议,也是唯一能够妥善处理好不相关路由域间的多路连接的协议. BGP 构建在 EGP ...
- 🔺 Garbage Remembering Exam UVA - 11637()
题目大意:给你N个单词,有两种方法随机排列,一种随机排成一行,另一种随机排成一圈,当两个单词之间的距离在两种排列中都严格小于K时,则这两个单词构成无效单词,问无效单词的期望. 解题思路:首先对于一排单 ...