JZOJ5755. 「SDOI2018」战略游戏

题意

省选临近,放飞自我的小 Q 无心刷题,于是怂恿小 C 和他一起颓废,玩起了一款战略游戏。

这款战略游戏的地图由 n个城市以及 m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到任意其他城市。现在小 C 已经占领了其中至少两个城市,小 Q 可以摧毁一个小 C 没占领的城市,同时摧毁所有连接这个城市的道路。只要在摧毁这个城市之后能够找到某两个小 C 占领的城市 u和 v,使得从 u出发沿着道路无论如何都不能走到 v,那么小 Q 就能赢下这一局游戏。

小 Q 和小 C 一共进行了 q局游戏,每一局游戏会给出小 C 占领的城市集合 S,你需要帮小 Q 数出有多少个城市在他摧毁之后能够让他赢下这一局游戏。

数据范围

对于 30%的分数, ∑∣S∣≤20。
对于另外 45%的分数,对于每一次询问,满足 ∣S∣=2 。
对于 100% 的分数, 1≤T≤10,2≤n≤10^5,n−1≤m≤2×10^5,1≤q≤10^5,且对于每组测试数据,有 ∑∣S∣≤2×10^5。

Analysis

首先,让图不连通,那么肯定跟割点和双连通分量有关系了。一次询问多个点,事实上就是点与点之间路径的并上割点的数量,但在图上我们十分不好做。考虑套路的用圆方树转换,所谓圆方树,就是将点双连通分量破开,建一个新点为方点,其原双连通分量所有点向其连边,原图上所有点为原点。那么不难发现,重建之后的图为森林,且能够代表原图的信息。建出圆方树后,给出一堆点的询问,又套路的套上虚树,不难发现,答案就是虚树上原点的数量,这题就解决了。

Code

# include<cstdio>
# include<cstring>
# include<algorithm>
# include<vector>
using namespace std;
# define pb push_back
const int N = 4e5 + 5;
vector <int> g[N],g1[N << 1];
int f[N][25],val[N],dfn[N],low[N];
int sta[N],fa[N],dep[N],a[N],t[N];
int n,m,tot,num,T,q,top;
inline int read()
{
    int x = 0; char ch = getchar();
    for (; ch < '0' || ch > '9' ; ch = getchar());
    for (; ch >= '0' && ch <= '9' ; ch = getchar()) x = x * 10 + ch - '0';
    return x;
}
bool cmp(int x,int y) { return dfn[x] < dfn[y]; }
inline void tarjan(int x)
{
    dfn[x] = low[x] = ++num;
    sta[++top] = x;
    for (int i = 0 ; i < g[x].size() ; ++i)
    {
        int v = g[x][i];
        if (!dfn[v])
        {
            tarjan(v);
            low[x] = min(low[x],low[v]);
            if (low[v] >= dfn[x])
            {
                ++tot;
                while (sta[top] != v)
                {
                    g1[sta[top]].pb(tot),g1[tot].pb(sta[top]);
                    --top;
                }
                g1[sta[top]].pb(tot),g1[tot].pb(sta[top]); --top;
                g1[x].pb(tot),g1[tot].pb(x);
            }
        }else low[x] = min(low[x],dfn[v]);
    }
}
inline void dfs(int x)
{
    dfn[x] = ++num,dep[x] = dep[f[x][0]] + 1,val[x] = val[f[x][0]] + (x <= n);
    for (int i = 1 ; i <= 20 ; ++i) f[x][i] = f[f[x][i - 1]][i - 1];
    for (int i = 0 ; i < g1[x].size() ; ++i)
    {
        int v = g1[x][i];
        if (v == f[x][0]) continue;
        f[v][0] = x,dfs(v);
    }
}
inline int getlca(int x,int y)
{
    if (dep[x] < dep[y]) swap(x,y);
    for (int i = 20 ; ~i ; --i)
        if (dep[f[x][i]] >= dep[y]) x = f[x][i];
    if (x == y) return x;
    for (int i = 20 ; ~i ; --i)
        if (f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
    return f[x][0];
}
inline void solve()
{
    top = 0;
    int cnt = read(),h = 0,ans = 0;
    for (int i = 1 ; i <= cnt ; ++i) t[++h] = a[i] = read();
    sort(a + 1,a + cnt + 1,cmp);
    for (int i = 1 ; i <= cnt ; ++i)
    {
        if (!top) sta[++top] = a[i];
        else
        {
            int x = a[i],ft = getlca(x,sta[top]);
            while (dfn[sta[top]] > dfn[ft])
            {
                if (dfn[sta[top - 1]] <= dfn[ft])
                {
                    fa[sta[top--]] = ft;
                    if (sta[top] != ft) sta[++top] = ft,t[++h] = ft;
                    break;
                }else fa[sta[top]] = sta[top - 1],--top;
            }
            sta[++top] = x;
        }
    }
    while (top > 1) fa[sta[top]] = sta[top - 1],--top;
    sort(t + 1,t + h + 1,cmp);
    for (int i = 2 ; i <= h ; ++i) ans += val[t[i]] - val[fa[t[i]]],fa[t[i]] = 0;
    fa[t[1]] = 0;
    printf("%d\n",ans + (t[1] <= n) - cnt);
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    T = read();
    while (T--)
    {
        n = read(),m = read(); tot = n;
        for (int i = 1 ; i <= m ; ++i)
        {
            int u = read(),v = read();
            g[u].pb(v),g[v].pb(u);
        }
        tarjan(1); num = 0;
        dfs(1);
        q = read();
        while (q--) solve();
        for (int i = 1 ; i <= n ; ++i) g[i].clear();
        for (int i = 1 ; i <= tot ; ++i) val[i] = f[i][0] = dfn[i] = low[i] = 0,g1[i].clear();
        top = num = tot = 0;
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值