[JZOJ3395]【NOIP2013模拟】Freda的传呼机

Description

为了 随时 与 rainbow快速交流, Freda制造了 两部传呼机 。Freda和 rainbow所在的地方有N座房屋、M条双向 光缆 。每条光缆连接两座房屋, 传呼机发出的信号只能沿着光缆传递,并且 传呼机的信号 从光缆的其中一端传递到另需要花费 t单位时间 。现在 Freda要 进行 Q次试验, 每次选取两座房屋,并想知道 传呼机的信号在这两座房屋之间传递 至少需 要多长时间。 Freda 和 rainbow简直弱爆了有木有T_TT_T ,请你帮他们吧……

N座房屋 通过光缆 一定是连通的, 并且这 M条光缆有以下三类连接情况:

A:光缆不形成环 ,也就是光缆仅 有 N-1条。

B:光缆只 形成一个环,也就是光缆 仅有 N条。

C:每条光缆仅在一个环中。

Solution

声明一下ABC类数据的多少

送分数据占10%,2<=N<=1000,N-1<=M<=1200。
A类数据占30%,M=N-1。
B类数据占50%,M=N。
C类数据占10%,M>N。
对于100%的数据,2<=N<=10000,N-1<=M<=12000,Q=10000,1<=x,y<=N,1<=t<=32768。

送分数据——直接 sp(b)fa ,稍微记忆一下

  • A类数据——树
  • B类数据——环套树
  • C类数据——仙人掌

仙人掌就是许多个环由链串在一起,每条边至多在一个环中。

先考虑A类
显然直接 ON 求出根到每个节点的距离再用 lca 之类的东西搞搞就好

B类环套树
环太恶心,我们考虑删边
你可以先用一个DFS把环搞出来,随便找一条必定在环上的边打上标记不能走
然后像树一样搞一遍,算最后答案的时候要取在树上的答案和强制走这条边的最小值。

关键在C类(话说只有10分啊!!栋爷:然而10分让你无法AC~~)
首先我们从根开始跑一遍 sp(b)fa ,求出每个点到根的最短距离,设之为 dis[]

现在我们需要把仙人掌弄成一个类似树的东西。
我们将从根 DFS 到的环上第一个点称为环顶(注意,环顶并不属于这个环,因为其有可能还会属于别的环)

DFS 的时候,如果我们碰到了一个已经走过的点,显然出现了环。
dfn[i] 表示第 i 个点在搜索树上的深度
设已走过的那个点是j

如果 dfn[j]>dfn[i] ,那么说明这个环已经处理过了,不理他。
如果 dfn[j]<dfn[i] ,显然 j 就是这个环的环顶。
我们需要记录一个D[i]表示第 i 个点单向到环顶的距离(随便什么方向,但一个环上的所有点的方向一定要一致),hf[i]表示 i 点所处的环的编号(不是环顶的编号!),lg[i]表示编号为 i 的环,这三个东西可以在找到环顶以后用一个过程递归回去(千万不能用原来的过程回溯!!!)

知道这个环的环顶以后,我们可以把每个点都与环顶连一条边,然后把环上其他边都删掉(也可以在刚刚的过程中顺便做)

这样,我们得到了一棵树,暂且称为仙人掌树

然后再来一个DFS处理出这棵树的一些东西,比如说父亲节点之类的。

现在考虑求解。
对于两个点 x,y ,设 lp=lca(x,y),u 表示从 x 向上走到lp的最后一个点(即 lp x 方向的儿子),v表示 y 方向的。

u,v不在同一个环上,那么 lp 必定在 x y的路径上,那么答案就是 dis[x]+dis[y]2dis[lp]

u,v 在同一个环上,那么显然 lp 也在这个环上。我们要取 u v的最短距离。
这时候刚才求得 D lg就发挥作用了。

先上图
这里写图片描述

假设 D 的方向是原图中的
那么u v 不经过lp就是 D[u]D[v] ,经过 lp 就是 D[u]+D[v]
取个 min 再加上 dis[x]dis[u] dis[y]dis[v] 就是答案

Code

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note
{
    int x,y,z,f,b;
};
bool cmp(note x,note y)
{
    return x.x<y.x;
}
int n,m,q,a1[200005][2],delt,num;
note a[100000];
int dis[100005],d[100005],ft[100005][15],deep[200005],hf[200005],fx[100005],lg[200005],from[100005];
bool bz[100005],b1;
void spfa(int l)
{
    int i,j,now;
    memset(bz,0,sizeof(bz));
    i=0;
    j=1;
    d[1]=l;
    bz[l]=1;
    dis[l]=0;
    while (i<j)
    {
        i++;
        int k;
        now=d[i];
        if (a1[now][0]>0)
        fo(k,a1[now][0],a1[now][1])
        {
            int p=a[k].y;
            if (dis[p]>dis[now]+a[k].z)
            {
                dis[p]=dis[now]+a[k].z;
                if (bz[p]==0) 
                {
                    d[++j]=p;
                    bz[p]=1;
                }
            }
        }
        bz[now]=0;
    }
}
void back(int k,int f)
{
    if (k!=delt)
    {
        d[k]=d[a[f].y]+a[f].z;
        a[f].b=1;
        if (a[from[k]].x!=delt)
        {
            m++;
            a[2*m-1].x=a[2*m].y=delt;
            a[2*m-1].y=a[2*m].x=k;
            a[2*m-1].f=a[2*m].f=m;
            a[2*m-1].b=a[2*m].b=0;
        }
        hf[k]=num;
        lg[num]+=a[f].z;
        back(a[from[k]].x,from[k]);
    }
    else
    {
        lg[num]+=a[f].z;
        delt=0;
    }
} 
void findh(int k,int f,int dep)
{
    int i;
    deep[k]=dep;
    from[k]=f; 
    fo(i,a1[k][0],a1[k][1])
    {
        int p=a[i].y;
        if (a[i].f!=a[f].f)
        {
            if(deep[p]==0)
            {
                findh(p,i,dep+1);
            } 
            else if (deep[k]>deep[p])
            {
                delt=p;
                hf[k]=++num;
                d[k]=a[i].z;
                lg[num]+=a[i].z;
                back(a[f].x,f);
            }
        }
    } 
}
void dfs(int k,int f,int dep)
{
    ft[k][0]=f;
    deep[k]=dep;
    int i;
    fo(i,a1[k][0],a1[k][1])
    {
        int p=a[i].y;
        if (p!=f&&a[i].b==0&&a[a[i].f].b==0)
        {
            dfs(p,k,dep+1); 
        } 
    }
} 
int lca(int x,int y,int &u,int &v)
{
    if (deep[x]<deep[y]) swap(x,y);
    while(deep[x]!=deep[y])
    {
        int i=0; 
        while(deep[ft[x][i]]>deep[y]) i++;
        if (i==0) 
        {
            if (ft[x][0]==y) 
            {
                u=v=x;
                return ft[x][0];
            }
            x=ft[x][0];
            break;
        }
        x=ft[x][--i];
    }
    while (x!=y)
    {
        int i=0;
        while (ft[x][i]!=ft[y][i]) i++;
        if (i==0) 
        {
            u=x;
            v=y;
            return ft[x][0];
        }
        x=ft[x][--i];
        y=ft[y][i];
    }
}
int main()
{
    freopen("call.in","r",stdin);
    freopen("call.out","w",stdout);
    cin>>n>>m>>q;
    int i,j;
    fo(i,1,m) 
    {
        scanf("%d%d%d",&a[2*i-1].x,&a[2*i-1].y,&a[2*i-1].z);
        a[2*i].x=a[2*i-1].y;
        a[2*i].y=a[2*i-1].x;
        a[2*i].z=a[2*i-1].z;
        a[2*i].f=a[2*i-1].f=i;
        a[2*i].b=a[2*i-1].b=0;
    }
    sort(a+1,a+2*m+1,cmp);
    fo(i,1,2*m)
    {
        if (a[i].x!=a[i-1].x)
        {
            a1[a[i].x][0]=i;
            a1[a[i-1].x][1]=i-1;
        }
    }
    a1[a[2*m].x][1]=2*m;
    memset(dis,107,sizeof(dis));
    bz[1]=1;
    spfa(1);
    num=0;
    delt=0;
    findh(1,0,1);
    sort(a+1,a+2*m+1,cmp);
    fo(i,1,2*m)
    {
        if (a[i].x!=a[i-1].x)
        {
            a1[a[i].x][0]=i;
            a1[a[i-1].x][1]=i-1;
        }
        if (fx[a[i].f]==0) fx[a[i].f]=i;
        else 
        {
            a[fx[a[i].f]].f=i;
            a[i].f=fx[a[i].f];
        }
    }
    a1[a[2*m].x][1]=2*m;
    memset(deep,0,sizeof(deep));
    dfs(1,0,1);
    fo(i,1,trunc(log(n)/log(2)))
            fo(j,1,n) ft[j][i]=ft[ft[j][i-1]][i-1];
    fo(i,1,q)
    {
        int x,y,u,v;
        scanf("%d%d",&x,&y);
        if (x==y)
        {
            printf("0\n");
            continue;
        }
        int lp=lca(x,y,u,v);
        if (hf[u]!=hf[v]||hf[u]==0||hf[v]==0) printf("%d\n",dis[x]+dis[y]-2*dis[lp]);
        else
        {
            if (u==v) printf("%d\n",abs(dis[x]-dis[y]));
            else 
            {
                int dx=dis[x]-dis[u],dy=dis[y]-dis[v],duv=abs(d[u]-d[v]);
                printf("%d\n",min(duv,lg[hf[u]]-duv)+dx+dy);
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值