BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)

Description

省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏。
这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到任意其他城市。
现在小C已经占领了其中至少两个城市,小Q可以摧毁一个小C没占领的城市,同时摧毁所有连接这个城市的道路。
只要在摧毁这个城市之后能够找到某两个小C占领的城市u和v,使得从u出发沿着道路无论如何都不能走到v,那么小Q就能赢下这一局游戏。
小Q和小C一共进行了q局游戏,每一局游戏会给出小C占领的城市集合S。你需要帮小Q数出有多少个城市在他摧毁之后能够让他赢下这一局游戏。

Input

第一行包含一个正整数T,表示测试数据的组数,
对于每组测试数据,
第一行是两个整数n和m,表示地图的城市数和道路数,
接下来m行,每行包含两个整数u和v~(1<=u<v<=n)
表示第u个城市和第v个城市之间有一条道路,同一对城市之间可能有多条道路连接,
第m+1是一个整数q,表示游戏的局数,
接下来q行,每行先给出一个整数|S|(2<=|S|<=n)
表示小C占领的城市数量,然后给出|S|个整数s1,s2,...s|S|,(1<=s1<s2<s|S|<=n),表示小C占领的城市。
1<= T<= 10,
2<= n<= 10^5 且 n-1<= m<= 2*10^5,
1<= q<= 10^5,
对于每组测试数据,有Sigma|S|<= 2*10^5

Output

对于每一局游戏,输出一行,包含一个整数,表示这一局游戏中有多少个城市在小Q摧毁之后能够让他赢下这一局游戏。

Sample Input

2
7 6
1 2
1 3
2 4
2 5
3 6
3 7
3
2 1 2
3 2 3 4
4 4 5 6 7
6 6
1 2
1 3
2 3
1 4
2 5
3 6
4
3 1 2 3
3 1 2 6
3 1 5 6
3 4 5 6

Sample Output

0
1
3
0
1
2
3

Solution

突然发现我早就学过圆方树的建树方法啊……

我先大体说一下圆方树到底是个啥东东_(:зゝ∠)_

所谓的圆方树,就是把一个图变成一个树。具体怎么变呢?对于每一个点双,我们创立一个新的点,然后把这个点双内的点连接到新点上。新点就是方点,旧点就是圆点。可以证明这样连接出来的一定是一棵树。盗一张图QAQ

这个图能够很直观的体现出圆方树的建立方法……

圆方树有那么几个性质。

1、一条边两端一定是一个圆点和一个方点。

2、原图中所有的割点,在圆方树中一定至少有两条出边,非割点一定有且只有一条出边。

3、一个圆点到圆点的简单路径代表了原图中两点之间简单路径的并。这个想想也很好懂。那么我们圆点维护点原本的信息,方点维护点双内的信息就可以处理路径问题了。举个例子:

一张图,每个点有个点权,求$u$点到$v$点的所有简单路径经过的最小权值。

按照上面所说,我们每个用方点维护点双内的最小点权,然后就相当于树上查询路径最小值了。

那么要修改的话呢?改一个圆点后,总不能暴力扫一遍周围的方点吧?

其实可以方点维护的信息不包括父亲圆点,这样修改一个圆点的时候只改一下父亲方点就好了。查询的时候如果$LCA$是方点,再把$lca$的父亲单独算进去就好了。

 

回到这个题,首先一看询问的$sigma$是$O(n)$级别的,那么十有八九就是得上虚树了。

我们先把圆方树建出来,然后每次询问建立虚树。

很容易发现,虚树上非关键点的圆点的个数就是答案。当然,也要包括虚树上每一条边在圆方树上经过的非关键圆点。

统计的时候注意根据写法搞好边界状态= =……写这个题才发现我之前的点双写的都是假的

Code

  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 #include<vector>
  5 #include<algorithm> 
  6 #define N (200009)
  7 using namespace std;
  8 
  9 struct Edge{int from,to,next;}edge[N<<1];
 10 int T,n,m,u,v,num_edge,dfs_num,BCC_num,top,k,q,ans;
 11 int head[N],ID[N],Low[N],DFN[N],stack[N],Cut[N];
 12 int vis[N],f[N][18],Depth[N],a[N],Sum[N],Key[N];
 13 vector<int>BCC[N];
 14 vector<int>E[N];
 15 bool cmp(int u,int v) {return DFN[u]<DFN[v];}
 16 
 17 void add(int u,int v)
 18 {
 19     edge[++num_edge].to=v;
 20     edge[num_edge].from=u;
 21     edge[num_edge].next=head[u];
 22     head[u]=num_edge;
 23 }
 24 
 25 void Tarjan(int x,int fa)
 26 {
 27     int son_num=0;
 28     Low[x]=DFN[x]=++dfs_num;
 29     for (int i=head[x]; i; i=edge[i].next)
 30         if (!DFN[edge[i].to])
 31         {
 32             son_num++; stack[++top]=i;
 33             Tarjan(edge[i].to,x);
 34             Low[x]=min(Low[x],Low[edge[i].to]);
 35             if (Low[edge[i].to]>=DFN[x])
 36             {
 37                 Cut[x]=true; ++BCC_num;
 38                 while (1)
 39                 {
 40                     int now=stack[top--];
 41                     if (ID[edge[now].from]!=BCC_num)
 42                     {
 43                         BCC[BCC_num].push_back(edge[now].from);
 44                         ID[edge[now].from]=BCC_num;
 45                     }
 46                     if (ID[edge[now].to]!=BCC_num)
 47                     {
 48                         BCC[BCC_num].push_back(edge[now].to);
 49                         ID[edge[now].to]=BCC_num;
 50                     }
 51                     if (edge[now].from==x && edge[now].to==edge[i].to) break;
 52                 }
 53             }
 54         }
 55         else if (edge[i].to!=fa)
 56             Low[x]=min(Low[x],DFN[edge[i].to]);
 57     if (!fa) Cut[x]=(son_num>1);
 58 }
 59 
 60 void Build_Tree1()//建圆方树 
 61 {
 62     Tarjan(1,0);
 63     memset(head,0,sizeof(head)); num_edge=0;
 64     for (int i=1; i<=BCC_num; ++i)
 65         for (int j=0,sz=BCC[i].size(); j<sz; ++j)
 66             add(i+n,BCC[i][j]), add(BCC[i][j],i+n), vis[BCC[i][j]]=1;
 67 }
 68 
 69 void DFS(int x,int fa)
 70 {
 71     f[x][0]=fa; Depth[x]=Depth[fa]+1;
 72     DFN[x]=++dfs_num; Sum[x]=Sum[fa]+vis[x];
 73     for (int i=1; i<=17; ++i)
 74         f[x][i]=f[f[x][i-1]][i-1];
 75     for (int i=head[x]; i; i=edge[i].next)
 76         if (edge[i].to!=fa) DFS(edge[i].to,x);
 77 }
 78 
 79 int LCA(int x,int y)
 80 {
 81     if (Depth[x]<Depth[y]) swap(x,y);
 82     for (int i=17; i>=0; --i)
 83         if (Depth[f[x][i]]>=Depth[y]) x=f[x][i];
 84     if (x==y) return x;
 85     for (int i=17; i>=0; --i)
 86         if (f[x][i]!=f[y][i]) x=f[x][i], y=f[y][i];
 87     return f[x][0];
 88 }
 89 
 90 void ADD(int u,int v)
 91 {
 92     if (u==v) return;
 93     E[u].push_back(v);
 94     ans+=Sum[v]-Sum[u];
 95 }
 96 
 97 void Insert(int x)
 98 {
 99     if (top==1) {stack[++top]=x; return;}
100     int lca=LCA(x,stack[top]);
101     if (lca==stack[top]) {stack[++top]=x; return;}
102     while (top>1 && DFN[stack[top-1]]>=DFN[lca])
103         ADD(stack[top-1],stack[top]), top--;
104     if (lca!=stack[top]) ADD(lca,stack[top]), stack[top]=lca;
105     stack[++top]=x;
106 }
107 
108 void Build_Tree2()//建虚树 
109 {
110     stack[top=1]=1;
111     for (int i=1; i<=k; ++i) Insert(a[i]);
112     while (top>=2) ADD(stack[top-1],stack[top]), top--;
113     ans+=vis[1]; 
114     if (!Key[1] && E[1].size()==1)
115         ans-=Sum[f[E[1][0]][0]];
116 }
117 
118 void Clear(int x)
119 {
120     Key[x]=0;
121     for (int sz=E[x].size(),i=0; i<sz; ++i)
122         Clear(E[x][i]);
123     E[x].clear();
124 }
125 
126 void MEMSET()
127 {
128     memset(ID,0,sizeof(ID));
129     memset(DFN,0,sizeof(DFN));
130     memset(Low,0,sizeof(Low));
131     memset(Cut,0,sizeof(Cut));
132     memset(vis,0,sizeof(vis));
133     memset(head,0,sizeof(head)); num_edge=0;
134     for (int i=1; i<=n; ++i) BCC[i].clear();
135     dfs_num=0; BCC_num=0; top=0; ans=0;
136 }
137 
138 int main()
139 {
140     scanf("%d",&T);
141     while (T--)
142     {
143         MEMSET();
144         scanf("%d%d",&n,&m);
145         for (int i=1; i<=m; ++i)
146         {
147             scanf("%d%d",&u,&v);
148             add(u,v); add(v,u);
149         }
150         Build_Tree1();
151         dfs_num=0; DFS(1,0);
152         scanf("%d",&q);
153         for (int i=1; i<=q; ++i)
154         {
155             ans=0;
156             scanf("%d",&k);
157             for (int j=1; j<=k; ++j)
158                 scanf("%d",&a[j]), Key[a[j]]=1;
159             sort(a+1,a+k+1,cmp);
160             Build_Tree2();
161             printf("%d\n",ans-k); Clear(1);
162         }
163     }
164 }

转载于:https://www.cnblogs.com/refun/p/10073618.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值