【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;
}

<树链剖分+线段树>

#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;
}

SPFA+链剖+线段树

#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+链剖+线段树

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

Dijkstra+Heap 2.5s

SPFA+SLF 10s

SPFA TLE

时间: 2024-10-21 20:20:41

【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[Submit][Status] Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第i行包含一个数:从牛棚_1到牛棚_i+1并且避免从牛棚1到牛棚i+1最短路经上最后一条牛

【BZOJ 1576】 [Usaco2009 Jan]安全路经Travel

1576: [Usaco2009 Jan]安全路经Travel Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 676  Solved: 231 [Submit][Status] Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第i行包含一个数:从牛棚_1到牛棚_i+1并且避免从牛棚1到牛棚i+1最短路经上最后一条

BZOJ1576 [Usaco2009 Jan]安全路经Travel

首先用Dijkstra做出最短路生成树,设dis[p]为1到p点的最短路长度 对于一条不在生成树上的边u -> v,不妨设fa为u.v的lca 则一fa到v的路径上的任意点x都可以由u达到,走的方式是1 -> fa -> u -> v -> x,dis'[x] = dis[u] + dis(u, v) + dis[v] - dis[x] 于是可以用dis[u] + dis(u, v) + dis[v]更新fa到v的路径上的所有点 链剖一下,线段树lazytag就好了,连pus

[Usaco2009 Jan]安全路经Travel BZOJ1576 Dijkstra+树链剖分+线段树

分析: Dijkstra求最短路树,在最短路树上进行操作,详情可见上一篇博客:http://www.cnblogs.com/Winniechen/p/9042937.html 我觉得这个东西不压行写出了有点丑...之后写了一个压行后更丑的... 附上压行后的代码: #include <cstdio> #include <algorithm> #include <cmath> #include <cstdlib> #include <cstring>

[Usaco2009 Jan]安全路经Travel

安全路径 题意 Solution 嗯,首先既然不能经过最后一条边,那么我们考虑建出一个最短路树 然后非树边\(u,v,w\),只能影响到\(u->v\)这个路径上的点,因为只能往回跑 那么考虑怎么更新.一个显然的办法就是把边按照某种顺序排列,然后用并查集标记一下哪个点访问过了,可以做到\(O(n)\) 那么按照什么顺序排序呢?我们设一个点\(u\)到1的距离(即最短路长度)为\(dis_u\) 那么对于一条非树边\(u->v\),设路径上其中一个点为\(x\),那么这个"不经过最后一

bzoj 1574: [Usaco2009 Jan]地震损坏Damage

Description 农夫John的农场遭受了一场地震.有一些牛棚遭到了损坏,但幸运地,所有牛棚间的路经都还能使用. FJ的农场有P(1 <= P <= 30,000)个牛棚,编号1..P. C(1 <= C <= 100,000)条双向路经联接这些牛棚,编号为1..C. 路经i连接牛棚a_i和b_i (1 <= a_i<= P;1 <= b_i <= P).路经可能连接a_i到它自己,两个牛棚之间可能有多条路经.农庄在编号为1的牛棚. N (1 <

bzoj 1575: [Usaco2009 Jan]气象牛Baric

Description 为了研究农场的气候,Betsy帮助农夫John做了N(1 <= N <= 100)次气压测量并按顺序记录了结果M_1...M_N(1 <= M_i <= 1,000,000). Betsy想找出一部分测量结果来总结整天的气压分布. 她想用K(1 <= K <= N)个数s_j (1 <= s_1 < s_2 < ... < s_K <= N)来概括所有测量结果. 她想限制如下的误差: 对于任何测量结果子集,每一个非此

bzoj 3396: [Usaco2009 Jan]Total flow 水流【最大流】

最大流生动形象的板子,注意数组开大点 #include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=100,inf=1e9; int n=26,m,h[N],cnt=1,s=1,t=26,le[N],x; char s1[5],s2[5]; struct qwe { int ne,to,va; }e[N*N*2];

1574: [Usaco2009 Jan]地震损坏Damage

1574: [Usaco2009 Jan]地震损坏Damage Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 425  Solved: 232[Submit][Status][Discuss] Description 农夫John的农场遭受了一场地震.有一些牛棚遭到了损坏,但幸运地,所有牛棚间的路经都还能使用. FJ的农场有P(1 <= P <= 30,000)个牛棚,编号1..P. C(1 <= C <= 100,000)条双向路经