P4606 [SDOI2018]战略游戏

【题意】

给出一个无向图,q次询问,每次给定一个点集s代表占领点,问有多少个未被占领的点可以作为点集s中两个点u,v的割点

【分析】

首先,先建立圆方树,问题转化为能包含 给定点集 的最小连通块的 圆点个数 - 占领点个数,也就是点集中两两点的并集的点个数-占领点个数

然后,按照圆方树的套路,我们要给点赋值,显然圆点赋1,方点赋0即可,然后把点权转移到父亲边的边权上去。

接着,我们需要利用类似虚树的套路,也算是一个小技巧,将询问点集按照dfs序排序,相邻两两简单路径长度之和+首尾简单路径长度(也就是围成一个圆,相邻两人的距离之和)除以二即可

那么最终答案为记得加上根节点(首尾的lca)的点权,这是点权转化为边权中丢失的部分

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
vector <int> G[maxn],YFT[maxn]; 
int n,m;
int dfn[maxn],low[maxn],dfs_time;
int in[maxn],st[maxn],top;
int sq;
void add(int x,int y)
{
    YFT[x].push_back(y);
}
void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++dfs_time;
    in[u]=1; st[++top]=u;
    for(auto to:G[u])
    {
        if(to==fa || dfn[to]>dfn[u]) continue;
        if(!dfn[to])
        {
            tarjan(to,u);
            low[u]=min(low[u],low[to]);
        }
        else if(in[to]) {low[u]=min(low[u],dfn[to]); continue; }
        if(low[to]==dfn[u])
        {
            sq++;
            add(u,sq);
            while(st[top]!=to)
            {
                in[st[top]]=0; add(sq,st[top]);
                top--;
            }
            add(sq,to); in[to]=0; top--;   
        }
        else if(low[to]>dfn[u])
        {
            add(u,to); top--; in[to]=0;
        }
    }
}
int dep[maxn],dis[maxn],rk;
int f[maxn][19];
void dfs(int u,int fa)
{
    dfn[u]=++rk;
    for(auto to:YFT[u])
    {
        // if(to==fa) continue;
        dep[to]=dep[u]+1; dis[to]=dis[u]+(to<=n);
        f[to][0]=u;
        for(int i=1;i<=18;i++) f[to][i]=f[f[to][i-1]][i-1];
        dfs(to,u);
    }
}
int getlca(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=18;i>=0;i--)
        if(dep[f[x][i]]>=dep[y])
            x=f[x][i];
    if(x==y) return x;
    for(int i=18;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int getdis(int x,int y)
{
    return dis[x]+dis[y]-2*dis[getlca(x,y)];
}
int qu[maxn];
bool cmp(int x,int y)
{
    return dfn[x]<dfn[y];
}
int main()
{
    // freopen("a.in","r",stdin);
    // freopen("a.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        int x,y;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            G[x].push_back(y); G[y].push_back(x);
        }
        for(int i=1;i<=n;i++) dfn[i]=low[i]=in[i]=0; top=dfs_time=0; sq=n;
        tarjan(1,-1);
        dep[1]=0,dis[1]=0,rk=0;
        dfs(1,0);
        int q;
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            int num;
            scanf("%d",&num);
            for(int j=1;j<=num;j++) scanf("%d",&qu[j]);
            sort(qu+1,qu+num+1,cmp);
            int sum=getdis(qu[1],qu[num]);
            for(int j=1;j<num;j++)
                sum+=getdis(qu[j],qu[j+1]);
            // printf("[%d] ",sum);
            printf("%d\n",sum/2-num+(getlca(qu[1],qu[num])<=n));
        }
        for(int i=1;i<=n;i++) G[i].clear();
        for(int i=1;i<=sq;i++) YFT[i].clear(),dfn[i]=0;
        // return 0;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值