【题解】SDOI2018战略游戏

  这题还是蛮简单的,被CNST的大小卡了好久。一定要开到18呀……

  首先,遇到这种带各种各样环的图先考虑是不是可以建立圆方树,在圆方树上求出答案。然后转化为圆方树之后,我们就将图转化到了树上。答案非常的明显:只要一个圆点位于一个节点到另一个节点的路径上,它就是一个可以选择的答案点。

  又观察到数据范围中给出的总和 <= & 多组询问的模式,立马联想到建立虚树。建立出了虚树,我们发现这棵虚树有一个非常妙妙的性质:所有的叶子节点均为指定点。这样的话,在这棵虚树上所有的点(从叶子到根的路径上的点,包括没有建出来的点)均为合法的答案。不过要注意到因为我们自动建立出了1点为根节点,所以要防止1没有被选择,要减去这一段非法的点数。

// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;
#define maxn 200000
#define CNST 19
int n, m, K, tot, T;
int timer, dfn[maxn], low[maxn];
int dep[maxn], dis[maxn], gra[maxn][CNST];
int ans, S[maxn], a[maxn];
bool vis[maxn];

struct edge
{
    int cnp = 1, head[maxn], to[maxn * 2], last[maxn * 2];
    void add(int u, int v)
    {
        if(u == v) return;
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
        to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++;
    }
    void Clear()
    {
        cnp = 1; memset(head, 0, sizeof(head));
    }
}E1, E2, E3;

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

void Tarjan(int u)
{
    dfn[u] = low[u] = ++ timer; S[++ S[0]] = u;
    for(int i = E1.head[u]; i; i = E1.last[i])
    {
        int v = E1.to[i];
        if(!dfn[v])
        {
            Tarjan(v); low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u])
            {
                E2.add(++ tot, u); int x = 0;
                do
                {
                    x = S[S[0] --]; E2.add(tot, x);
                }while(x != v);
            }
        }
        else low[u] = min(low[u], dfn[v]);
    }
}

void dfs(int u, int fa)
{
    dis[u] = 0, dep[u] = 0;
    gra[u][0] = fa, dfn[u] = ++ timer, dep[u] = dep[fa] + 1; 
    if(u <= n) dis[u] += 1; dis[u] += dis[fa];
    for(int i = 1; i < CNST; i ++) gra[u][i] = gra[gra[u][i - 1]][i - 1];
    for(int i = E2.head[u]; i; i = E2.last[i])
    {
        int v = E2.to[i];
        if(v != fa) dfs(v, u);
    }
}

int LCA(int x, int y)
{
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = CNST - 1; ~i; i --) 
        if(dep[gra[x][i]] >= dep[y]) x = gra[x][i];
    for(int i = CNST - 1; ~i; i --)
        if(gra[x][i] != gra[y][i]) 
            x = gra[x][i], y = gra[y][i];
    return x == y ? x : gra[x][0];
}

bool cmp(int a, int b)
{
    return dfn[a] < dfn[b]; 
}

void DP(int u, int fa)
{
    for(int i = E3.head[u]; i; i = E3.last[i])
    {
        int v = E3.to[i];
        if(v == fa) continue; 
        DP(v, u); ans += dis[v] - dis[u]; 
    }
    E3.head[u] = 0;
}

void Work()
{
    E3.cnp = 1; 
    K = read(), tot = 1, S[1] = 1, S[0] = 1;
    for(int i = 1; i <= K; i ++) a[i] = read();
    sort(a + 1, a + 1 + K, cmp);
    int L = a[1];
    for(int i = 1; i <= K; i ++)
    {
        int lca = LCA(S[S[0]], a[i]);
        L = LCA(L, a[i]);
        while(23333)
        {
            if(dep[lca] >= dep[S[S[0] - 1]])
            {
                E3.add(S[S[0]], lca); S[0] --;
                if(lca != S[S[0]]) S[++ S[0]] = lca;
                break;
            }
            if(S[0]) E3.add(S[S[0]], S[S[0] - 1]), S[0] --;
        }
        S[++ S[0]] = a[i];
    }
    while(S[0] > 1) E3.add(S[S[0]], S[S[0] - 1]), S[0] --;
    ans = 0; DP(1, 0);
    if(L > n) printf("%d\n", ans - dis[L] + 1 - K);
    else printf("%d\n", ans - dis[L] + 2 - K);
}

void init()
{
    E1.Clear(), E2.Clear(), timer = 0;
    memset(gra, 0, sizeof(gra)); 
}

int main()
{
    scanf("%d", &T);
    while(T --)
    {
        init();
        tot = n = read(), m = read();
        for(int i = 1; i <= n; i ++) dfn[i] = low[i] = 0;
        for(int i = 1; i <= m; i ++)
        {
            int u = read(), v = read();
            E1.add(u, v);
        }
        S[0] = 0, Tarjan(1); 
        int Q = read();
        timer = 0; dfs(1, 0);
        for(int i = 1; i <= Q; i ++) Work();
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/twilight-sx/p/9240537.html