洛谷P1197 [JSOI2008]星球大战

https://www.luogu.org/problem/show?pid=1197
思路
首先动态求割点会TLE,考虑倒序离线操作。
当所有打击完成后,Tarjin统计同一连通分量上的点,并查集维护连通性(实际上来说只要两点之间有边则将之合并即可Tarjin处理麻烦反而会MLE?..不清楚为什么)。再依次将被毁灭的星球复原,则对答案的影响只与当前星球连接的星球数量(及连通关系)有关,每次统计当前连通块个数。
总而言之写麻烦了…

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,m,tot,cnt,num,root,ru,rv,k,sum;
int first[500010],nxt[500010],dfn[200010],low[200010],sccno[200010],stak[500010],fa[200010],xs[200010],scc[200010],ans[200010];
bool cut[200010],b[200010];
struct edge
{
    int u,v;
}l[500010];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void build(int f,int t)
{
    l[++tot]=(edge){f,t};
    nxt[tot]=first[f];
    first[f]=tot;
}
void dfs(int k)
{
    dfn[k]=low[k]=++tot;
    stak[++num]=k;
    for(int i=first[k];i!=-1;i=nxt[i])
    {
        int x=l[i].v;
        if(cut[x])
        continue;
        if(!dfn[x])
        {
            dfs(x);
            low[k]=min(low[k],low[x]);
        }
        else if(!sccno[x])
        low[k]=min(low[k],dfn[x]);
    }
    if(low[k]==dfn[k])
    {
        cnt++;
        while(1)
        {
            int p=stak[num--];//将打击完成后图中同一连通块内元素合并 
            int u=find(p);    //其实只要两点之间有边合并即可不需要Tarjin... 
            int v=find(k);
            if(u!=v)
            fa[u]=k;
            sccno[p]=cnt;
            if(stak[num+1]==k)
            break;
        }
    }
}
int main()
{
    memset(first,-1,sizeof(first));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&ru,&rv);
        build(ru,rv);
        build(rv,ru);
    }
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        scanf("%d",&xs[i]);
        cut[xs[i]]=1;
    }
    for(int i=0;i<=n-1;i++)
    fa[i]=i;
    tot=0;
    for(int i=0;i<=n-1;i++)
    {
        if(!dfn[i]&&!cut[i])//去除被毁灭的星球 
        root=i,dfs(i);
    }
    sum=cnt;
    num=0;
    for(int i=k;i>=1;i--)//打击完成后倒序处理 
    {
        tot=0;
        int h=xs[i];
        cut[h]=0;//复原 
        for(int i=first[h];i!=-1;i=nxt[i])//便利出边 
        {
            int x=l[i].v;
            if(cut[x])//仍处于毁灭状态的不可合并 
            continue;
            int p=find(x);
            if(!b[p])//每个集合只合并一次,亦可以通过找fa判断 
            {
                tot++;//统计此次合并的集合个数除了h点 
                scc[tot]=p;
                b[p]=1;
            }
        }
        for(int i=1;i<=tot;i++)
        fa[scc[i]]=h,scc[i]=0;
        cnt=cnt-tot+1;//+1为加上h点 
        ans[++num]=cnt;//统计当前答案 
    }
    for(int i=num;i>=1;i--)//转正序输出 
    printf("%d\n",ans[i]);
    printf("%d\n",sum);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值