洛谷 1710

   这道题是一道图论题,题目的意思是每次删除一条边,询问当前有多少点与1号节点的最短路的值与未删边之前不同。首先我们一定是求出原图的最短路,由于我们可以默认边权是1,于是我们可以BFS,这样我们就能够得到一张“最短路图”,所谓最短路图就是说对于一条边(u,v),我们只走dis[v]=dis[u]+1的边,这样如果删除的边不是最短路图上的边,对其他点是没有任何影响的,那我们考虑如果删除最短路图上的边,只要这条边的端点还在最短路图中能被1号点访问到,那么它的最短路一定不会变化,那我们怎么维护呢,感觉正着做很不好搞,因为删除一条边后,即使它的两端点不与1连通,它的两端点的点也可能与1连通,我们考虑正难则反,离线处理,将删边变成加边,维护一个bz数组表示每个点是否已经和1连通,这样,每加进一条边,我们判断它能否将1与边的出点连通,如果连通,我们就从这条边的出点DFS,将所有bz[i]=0的点变成1,对于每个bz[i]=1的点,我们不再进行DFS,这样就保证了每个点只被访问1次,也就保证了复杂度是线性的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 400005
int n,m,q,l=1,w=0;
int pre[maxn],other[maxn],last[maxn];
int pre1[maxn],other1[maxn],last1[maxn];
int dis[maxn],que[maxn],ans[maxn],query[maxn],cnt;
bool vis[maxn],flag[maxn],pd[maxn],bj[maxn];
struct edge
{
    int fr,to;    
}e[maxn];

int read(void)
{
    char ch=getchar();
    int x=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') 
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}

void connect(int x,int y)
{
    l++;
    pre[l]=last[x];
    last[x]=l;
    other[l]=y;    
}

void connect1(int x,int y)
{
    w++;
    pre1[w]=last1[x];    
    last1[x]=w;
    other1[w]=y;
}

void pre_bfs(void) 
{
    int h=1,t=1;
    vis[1]=1;que[1]=1;
    while (h<=t) 
    {
        int u=que[h];h++;
        for (int p=last[u];p;p=pre[p]) 
        {
            int v=other[p];
            if (vis[v]) continue;
            dis[v]=dis[u]+1;
            que[++t]=v;    
            vis[v]=1;
        }
    }
}

void bfs(void)
{
    memset(vis,0,sizeof vis);
    vis[1]=1;que[1]=1;
    int h=1,t=1;
    while (h<=t) 
    {
        int u=que[h];h++;
        for (int p=last[u];p;p=pre[p]) 
        {
            int v=other[p];
            if (dis[v]==dis[u]+1) flag[p/2]=1;
            if (vis[v]) continue;
            que[++t]=v;
            vis[v]=1;
        }
    }
}

void dfs(int u)
{
    cnt++;bj[u]=1;
    for (int p=last1[u];p;p=pre1[p]) 
    {
        int v=other1[p];
        if (bj[v]) continue;
        dfs(v);
    }
}

int main()
{
    //scanf("%d%d%d",&n,&m,&q);
    n=read();m=read();q=read();
    for (int i=1;i<=m;i++) 
    {
        int a,b;
        //scanf("%d%d",&a,&b);
        a=read();b=read();
        e[i].fr=a;e[i].to=b;
        connect(a,b);connect(b,a);    
    }
    pre_bfs();
    bfs();
    for (int i=1;i<=q;i++) 
    {
        int a;
        //scanf("%d",&a);
        a=read();
        pd[a]=1;query[i]=a;
    }
    for (int i=1;i<=m;i++) 
        if (dis[e[i].fr]>dis[e[i].to]) swap(e[i].fr,e[i].to);
    bj[1]=1;cnt=1;
    for (int i=1;i<=m;i++) 
        if (!pd[i]&&flag[i]) 
        {
            if (bj[e[i].fr]&&bj[e[i].to]) continue;
            connect1(e[i].fr,e[i].to);
            if (bj[e[i].fr]) dfs(e[i].to);    
        }
    ans[q]=n-cnt;
    for (int i=q-1;i>=1;i--) 
    {
        int num=query[i+1];
        if ((!(bj[e[num].fr]&&bj[e[num].to]))&&flag[num]) connect1(e[num].fr,e[num].to);
        if (bj[e[num].fr]&&flag[num]&&!bj[e[num].to]) dfs(e[num].to);
        ans[i]=n-cnt;    
    }
    for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;    
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值