[SDOI2018]战略游戏

题目描述

省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏。
这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到
任意其他城市。现在小C已经占领了其中至少两个城市,小Q可以摧毁一个小C没占领的城市,同时摧毁所有连接这
个城市的道路。只要在摧毁这个城市之后能够找到某两个小C占领的城市u和v,使得从u出发沿着道路无论如何都不
能走到v,那么小Q就能赢下这一局游戏。
小Q和小C一共进行了q局游戏,每一局游戏会给出小C占领的城市集合S
你需要帮小Q数出有多少个城市在他摧毁之后能够让他赢下这一局游戏。
题解
如果这是一棵树,那么做法就是直接对询问点见虚树,统计包含树中的点的最小联通块的大小。
现在它是一张无向图,那么就把圆方树建出来,考虑一条路径经过了圆->方->圆点,那么那两个圆点割点,应对答案有1的贡献,方点的贡献为0。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
#define N 200009
using namespace std;
typedef long long ll;
int dfn[N],head[N],tot,dis[N],n,low[N],ans,num,st[N],top,rbs[N],a[N];
bool vis[N];
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N<<1];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
vector<int>vec[N],ed[N];
inline bool cmp(int a,int b){return dfn[a]<dfn[b];}
struct LCA{
int top[N],size[N],son[N],fa[N],deep[N];
void clear(){memset(deep,0,sizeof(deep));memset(top,0,sizeof(top));memset(son,0,sizeof(son));memset(fa,0,sizeof(fa));}
void dfs(int u){
    size[u]=1;dis[u]=dis[fa[u]]+(u<=n);
    for(int i=0;i<vec[u].size();++i){
        int v=vec[u][i];
        fa[v]=u;deep[v]=deep[u]+1;
        dfs(v);size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}
void dfs2(int u){
    dfn[u]=++dfn[0];if(!top[u])top[u]=u;
    if(son[u])top[son[u]]=top[u],dfs2(son[u]);
    for(int i=0;i<vec[u].size();++i){
      int v=vec[u][i];
      if(v!=fa[u]&&v!=son[u])dfs2(v);
    }
}
inline int getlca(int u,int v){
    while(top[u]!=top[v]){
        if(deep[top[u]]<deep[top[v]])swap(u,v);
        u=fa[top[u]];
    }
    return deep[u]<deep[v]?u:v;
}
}lca;
void tarjan(int u,int fa){
    dfn[u]=low[u]=++dfn[0];vis[u]=1;st[++top]=u;
    for(int i=head[u];i;i=e[i].n)if((i^fa)!=1){
        int v=e[i].to;if(dfn[v]>dfn[u])continue;
        if(!dfn[v]){
            tarjan(v,i);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])low[u]=min(low[u],dfn[v]);
        if(dfn[u]==low[v]){
            ++num;vec[u].push_back(num);//cout<<u<<" "<<num<<endl;
            while(st[top]!=v){
                vec[num].push_back(st[top]);//cout<<num<<" "<<st[top]<<endl;
                vis[st[top]]=0;top--;
            }
            vec[num].push_back(v);vis[v]=0;top--;//cout<<num<<" "<<v<<endl;
        }
        if(low[v]>dfn[u]){
            vec[u].push_back(v);
            top--;vis[v]=0;
        }
    }
}
void dfs(int u){
    for(int i=0;i<ed[u].size();++i){
        int v=ed[u][i];
        dfs(v);
        ans+=dis[v]-dis[u]; 
        if(vis[v]&&v<=n)ans--;
    }
}
inline void init(){
    tot=1;top=0;
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(dis,0,sizeof(dis));
    lca.clear();
}
inline void solve(){
    n=rd();int m=rd();int u,v;
    init();
    for(int i=1;i<=m;++i){
        u=rd();v=rd();
        add(u,v);add(v,u);
    } 
    num=n;
    tarjan(1,0);
    memset(dfn,0,sizeof(dfn));
    top=0;
    lca.dfs(1);lca.dfs2(1);
    memset(vis,0,sizeof(vis));
    int q=rd();
    while(q--){
        int s=rd();ans=0;
        for(int i=1;i<=s;++i)a[i]=rd(),vis[a[i]]=1;
        sort(a+1,a+s+1,cmp);
        st[top=1]=rbs[rbs[0]=1]=a[1];int root=a[1];
        for(int i=2;i<=s;++i){
            int x=a[i];//cout<<x<<"guiu"<<endl;
            int l=lca.getlca(x,st[top]);
            if(l==st[top]){st[++top]=x;rbs[++rbs[0]]=x;continue;}
            while(top>1){
                int xx=st[top],yy=st[top-1];
                if(dfn[yy]<=dfn[l]){
                    ed[l].push_back(xx);//cout<<l<<" "<<xx<<endl;
                    top--;break;
                }
                ed[yy].push_back(xx);top--;//cout<<yy<<" "<<xx<<endl;
            }
            if(dfn[l]<dfn[st[top]]){
                if(dfn[l]<dfn[root])root=l;
            //    cout<<l<<" "<<st[top]<<endl;
                ed[l].push_back(st[top]);top--;
            }
            if(l!=st[top])st[++top]=l,rbs[++rbs[0]]=l;
            st[++top]=x;rbs[++rbs[0]]=x; 
        }
        while(top>1){
        //    cout<<st[top-1]<<" "<<st[top]<<endl;
            ed[st[top-1]].push_back(st[top]);
            top--;
        }
        if(!vis[root]&&root<=n)ans++;
        dfs(root);
        printf("%d\n",ans);
        while(rbs[0]){
            int x=rbs[rbs[0]];
            vis[x]=0;ed[x].clear();rbs[0]--;
        }        
    } 
    for(int i=1;i<=num;++i)vec[i].clear();
}
int main(){
    int T=rd();
    while(T--)solve();
    return 0;
} 

这里的圆方树不能随便的建,只能对每个点双开一个。、

具体建法为:对于非点双内的边,直接连,并且弹栈。

    if(low[v]>dfn[u]){
            vec[u].push_back(v);
            top--;vis[v]=0;
        }

对于每个割顶,新开一个节点,然后边弹栈边连边。

if(dfn[u]==low[v]){
            ++num;vec[u].push_back(num);
            while(st[top]!=v){
                vec[num].push_back(st[top]);
                vis[st[top]]=0;top--;
            }
            vec[num].push_back(v);vis[v]=0;top--;
        }

而且我们对于每个点双都只能算一次,为了避免算重,我们需要特判一下。

if(dfn[v]>dfn[u])continue

猜你喜欢

转载自www.cnblogs.com/ZH-comld/p/10384724.html