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

补充说明:【排序+并查集】是一种套路,处理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;
}
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

posted @ 2017-08-31 08:48  ONION_CYC  阅读(...)  评论(...编辑  收藏