LibreOJ #2562.「SDOI2018」战略游戏 圆方树+虚树

题意

给出一个n个点m条边的无向图,每次询问给出一个点集V,问有多少个点满足不属于V且在去掉该点后点集V中的点不连通。
n105,m2105 n ≤ 10 5 , m ≤ 2 ∗ 10 5

分析

先把原图的圆方树搞出来,不难发现答案就是给出的点集所构成的虚树上有多少个圆点。
直接搞就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=100005;

int n,m,cnt,last[N],dfn[N],low[N],sz,tot,a[N],top,stack[N*2],tim;
struct edge{int to,next;}e[N*4];

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

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

bool cmp(int x,int y);

struct Data
{
    int cnt,last[N*2],fa[N*2],dep[N*2],size[N*2],df[N*2],w[N*2],top[N*2];
    struct edge{int to,next;}e[N*4];

    void addedge(int u,int v)
    {
        e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
        e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
    }

    void dfs1(int x)
    {
        dep[x]=dep[fa[x]]+1;w[x]=w[fa[x]]+(x<=n);
        df[x]=++tim;size[x]=1;
        for (int i=last[x];i;i=e[i].next)
        {
            if (e[i].to==fa[x]) continue;
            fa[e[i].to]=x;
            dfs1(e[i].to);
            size[x]+=size[e[i].to];
        }
    }

    void dfs2(int x,int chain)
    {
        top[x]=chain;int k=0;
        for (int i=last[x];i;i=e[i].next)
            if (e[i].to!=fa[x]&&size[e[i].to]>size[k]) k=e[i].to;
        if (k) dfs2(k,chain);
        for (int i=last[x];i;i=e[i].next)
            if (e[i].to!=k&&e[i].to!=fa[x]) dfs2(e[i].to,e[i].to);
    }

    int get_lca(int x,int y)
    {
        while (top[x]!=top[y])
        {
            if (dep[top[x]]<dep[top[y]]) std::swap(x,y);
            x=fa[top[x]];
        }
        return dep[x]<dep[y]?x:y;
    }

    void solve()
    {
        int ans=1,top=0;bool flag=0;
        stack[++top]=1;
        std::sort(a+1,a+tot+1,cmp);
        if (a[1]==1) flag=1;
        for (int i=1;i<=tot;i++)
        {
            if (stack[top]==a[i]) continue;
            int lca=get_lca(a[i],stack[top]);
            if (lca==stack[top]) {stack[++top]=a[i];continue;}
            while (top>1&&dep[stack[top-1]]>=dep[lca]) ans+=w[stack[top]]-w[stack[top-1]],top--,flag|=(stack[top]==1);
            if (dep[stack[top]]>dep[lca]) ans+=w[stack[top]]-w[lca],flag|=(lca==1),top--;
            if (stack[top]!=lca) stack[++top]=lca;
            stack[++top]=a[i];
        }
        while (top>1) ans+=w[stack[top]]-w[stack[top-1]],top--;
        if (!flag) ans-=w[fa[stack[2]]];
        printf("%d\n",ans-tot);
    }
}t;

bool cmp(int x,int y)
{
    return t.df[x]<t.df[y];
}

void tarjan(int x,int fro)
{
    dfn[x]=low[x]=++tim;
    for (int i=last[x];i;i=e[i].next)
    {
        if (i==(fro^1)) continue;
        if (!dfn[e[i].to])
        {
            stack[++top]=i;
            tarjan(e[i].to,i);
            low[x]=std::min(low[x],low[e[i].to]);
            if (low[e[i].to]>=dfn[x])
            {
                sz++;int y=0;t.addedge(sz+n,x);
                while (y!=i)
                {
                    y=stack[top];top--;
                    t.addedge(sz+n,e[y].to);
                }
            }
        }
        else low[x]=std::min(low[x],dfn[e[i].to]);
    }
}

int main()
{
    int T=read();
    while (T--)
    {
        n=read();m=read();cnt=1;
        memset(last,0,sizeof(last));
        memset(dfn,0,sizeof(dfn));
        t.cnt=tim=sz=0;
        memset(t.last,0,sizeof(t.last));
        memset(t.fa,0,sizeof(t.fa));
        for (int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            addedge(x,y);
        }
        tarjan(1,0);
        tim=0;
        t.dfs1(1);
        t.dfs2(1,1);
        int q=read();
        while (q--)
        {
            tot=read();
            for (int i=1;i<=tot;i++) a[i]=read();
            t.solve();
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值