title
双倍经验(弱化版):LUOGU 4320 道路相遇
简化题意:
\(T\) 组数据,每组数据有一张无向图,\(q\) 个询问,每次给定一个集合 \(S\),求有多少个点满足删除这个点后,\(S\) 中至少有两个点不连通。
\(1\leqslant T\leqslant 10\)
\(2\leqslant n\leqslant 10^5,且n-1\leqslant m\leqslant 2×10^5\)
\(1\leqslant q\leqslant 10^5\)
对于每组测试数据,有 \(\sum|S|\leqslant 2 × 10^5\)
(弱化版中,\(|S|=2\) ,在本题中也能拿到 \(45pts\))
analysis
不得不说,这道题的模型还是比较好看出来的:无向图联通问题,还是和点有关的,那就想到了 \(tarjan\)求点双,再往下一想,哦,圆方树;每次询问又有关键点集合,那肯定是要虚树了。这样一来,这道题的模型就全部剖析完毕了。
第一种思路:
这种思路比较麻烦,因为上面的东西都要写,把原树的圆方树建出来,然后在圆方树上把每次询问的虚树建出来,然后在虚树上 \(dfs\),把所有 \(dis\) 都累加起来,就 \(OK\) 了。(嘴上 \(AC\) 还是挺简单的)
第二种思路:
这种思路和道路相遇一脉相承,所以先来看一下这个弱化版。
很明显,\(ans=\) 两点在圆方树上圆点的个数 \(=(dep[x]+dep[y]-dep[LCA(x,y)]*2)/2+1\)。
那么战略游戏呢?
很明显就是 \(S\) 在圆方树上虚树的圆点数 \(-|S|\)。
那么考虑把一个圆点的权值放在它到父亲方点的边上,那么把 \(S\) 按 \(dfs\) 序排序后,答案为
\[
\frac{dis(S_1,S_2)+dis(S_2,S_3)+...+dis(S_{n-1},S_n)+dis(S_n,S_1)}{2}-|S|
\]
这样的话,其实实际上就不需要把虚树求出来了,代码量大大减少。
code
战略游戏第一种思路:比较毒瘤的圆方树+虚树 \(Dp\),崩溃
#include<bits/stdc++.h>
const int maxn=2e5+10;
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++='\n';
}
}
using IO::read;
using IO::write;
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
struct Graph
{
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void Clear()
{
memset(head,0,sizeof(head));
len=0;
}
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
}G, A, T;
int n,m,Q,K,ans;
namespace Square
{
int dfn[maxn],low[maxn],id;
int Stack[maxn],top,tot;
inline void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
int k;
++tot;
do
{
k=Stack[top--];
A.add(tot,k), A.add(k,tot);
} while (k!=y);
A.add(tot,x), A.add(x,tot);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
}
using namespace Square;
namespace Virtual
{
int f[maxn][21];
int dist[maxn],dep[maxn];
inline void dfs(int x,int fa)
{
dfn[x]=++id;
for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
for (int i=A.head[x]; i; i=A.Next[i])
{
int y=A.ver[i];
if (y==fa) continue;
dist[y]=dist[x]+(y<=n);
f[y][0]=x;
dep[y]=dep[x]+1;
dfs(y,x);
}
}
inline int LCA(int x,int y)
{
if (dep[x]>dep[y]) std::swap(x,y);
for (int i=20; i>=0; --i)
if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
if (x==y) return x;
for (int i=20; i>=0; --i)
if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
inline bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
inline void insert(int x)
{
if (top==1) { Stack[++top]=x; return ; }
int lca=LCA(Stack[top],x);
if (lca==Stack[top]) { Stack[++top]=x; return ; }
while (dfn[Stack[top-1]]>=dfn[lca] && top>1)
{
T.add(Stack[top-1],Stack[top]), --top;
}
if (lca!=Stack[top]) T.add(lca,Stack[top]), Stack[top]=lca;
Stack[++top]=x;
}
int is[maxn];
inline void build(int *c,int k)
{
std::sort(c+1,c+k+1,cmp);
T.len=top=0;
if (is[1]^Q) Stack[top=1]=1;
for (int i=1; i<=k; ++i) insert(c[i]);
while (top>1) T.add(Stack[top-1],Stack[top]), --top;
}
int siz[maxn];
inline void Dp(int x)
{
siz[x]=(is[x]==Q);
int flag=0;
for (int i=T.head[x]; i; i=T.Next[i])
{
int y=T.ver[i];
Dp(y);
if (siz[y] && siz[y]!=K) ans+=dist[f[y][0]]-dist[x],++flag;
siz[x]+=siz[y];
}
if ( (siz[x]<K || flag>1) && x<=n && is[x]!=Q) ++ans;
T.head[x]=0;
}
}
using namespace Virtual;
inline void Clear()
{
G.Clear(), A.Clear();
id=top=tot=0;
memset(dep,0,sizeof(dep));
memset(f,0,sizeof(f));
memset(is,0,sizeof(is));
memset(dist,0,sizeof(dist));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(Stack,0,sizeof(Stack));
}
int main()
{
int t;read(t);
while (t--)
{
Clear();
read(n);read(m); tot=n;
for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
tarjan(1);
dist[1]=dep[1]=1, id=0; dfs(1,0);
for (read(Q); Q; --Q)//坑死人,要用未执行减减操作的 Q
{
read(K);
ans=0;
int *c=new int[K+10];
for (int i=1; i<=K; ++i) read(c[i]),is[c[i]]=Q;
build(c,K);
Dp(1);
write(ans);
delete[] c;
}
}
IO::flush();
return 0;
}
这是道路相遇的 \(code\)
#include<bits/stdc++.h>
const int maxn=2e6+10;
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++='\n';
}
}
using IO::read;
using IO::write;
template<typename T>inline T min(T a,T b) { return a<b ? a: b; }
struct Graph
{
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void Clear()
{
memset(head,0,sizeof(head));
len=0;
}
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
}G, A;
namespace Square
{
int dfn[maxn],low[maxn],id;
int Stack[maxn],top,tot;
inline void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
int k;
++tot;
do
{
k=Stack[top--];
A.add(tot,k), A.add(k,tot);
} while (k!=y);
A.add(tot,x), A.add(x,tot);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
}
using namespace Square;
namespace lca
{
int f[maxn][21],dep[maxn];
inline void dfs(int x,int fa)
{
dfn[x]=++id;
for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
for (int i=A.head[x]; i; i=A.Next[i])
{
int y=A.ver[i];
if (y==fa) continue;
f[y][0]=x;
dep[y]=dep[x]+1;
dfs(y,x);
}
}
inline int LCA(int x,int y)
{
if (dep[x]>dep[y]) std::swap(x,y);
for (int i=20; i>=0; --i)
if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
if (x==y) return x;
for (int i=20; i>=0; --i)
if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
}
using namespace lca;
int main()
{
int n,m;read(n);read(m); tot=n;
for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
tarjan(1);
id=0; dfs(1,0);
int Q;read(Q);
while (Q--)
{
int x,y;read(x);read(y);
int ans=( (dep[x]+dep[y]-(dep[LCA(x,y)]<<1) ) >>1)+1;
write(ans);
}
IO::flush();
return 0;
}
战略游戏第二种思路:和道路相遇思路一样
#include<bits/stdc++.h>
const int maxn=2e5+10;
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++='\n';
}
}
using IO::read;
using IO::write;
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
struct Graph
{
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void Clear()
{
memset(head,0,sizeof(head));
len=0;
}
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
}G, A;
namespace Square
{
int dfn[maxn],low[maxn],id;
int Stack[maxn],top,tot;
inline void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
int k;
++tot;
do
{
k=Stack[top--];
A.add(tot,k), A.add(k,tot);
} while (k!=y);
A.add(tot,x), A.add(x,tot);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
}
using namespace Square;
int n,m;
namespace lca
{
int f[maxn][21];
int dist[maxn],dep[maxn];
inline void dfs(int x,int fa)
{
dfn[x]=++id;
for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
for (int i=A.head[x]; i; i=A.Next[i])
{
int y=A.ver[i];
if (y==fa) continue;
dist[y]=dist[x]+(y<=n);
f[y][0]=x;
dep[y]=dep[x]+1;
dfs(y,x);
}
}
inline int LCA(int x,int y)
{
if (dep[x]>dep[y]) std::swap(x,y);
for (int i=20; i>=0; --i)
if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
if (x==y) return x;
for (int i=20; i>=0; --i)
if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
inline bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
inline int Dis(int x,int y)
{
return dist[x]+dist[y]-(dist[LCA(x,y)]<<1);
}
}
using namespace lca;
inline void Clear()
{
G.Clear(), A.Clear();
id=top=tot=0;
memset(dep,0,sizeof(dep));
memset(f,0,sizeof(f));
memset(dist,0,sizeof(dist));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(Stack,0,sizeof(Stack));
}
int main()
{
int t;read(t);
while (t--)
{
Clear();
read(n);read(m); tot=n;
for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
tarjan(1);
dist[1]=dep[1]=1, id=0; dfs(1,0);
int Q;read(Q);
while (Q--)
{
int k;read(k);
int *c=new int[k+10];
for (int i=1; i<=k; ++i) read(c[i]);
std::sort(c+1,c+k+1,cmp);
int ans=-2*k;
for (int i=1; i<k; ++i) ans+=Dis(c[i],c[i+1]);
ans+=Dis(c[1],c[k]);
if (LCA(c[1],c[k])<=n) ans+=2;
write(ans>>1);
delete[] c;
}
}
IO::flush();
return 0;
}