一棵树\(T\)和一张图\(G\),现在对图进行加边操作:每次找到\((a,b,c)\)满足\((a,b),(b,c)\in E_G\),且\(a,b,c\)任意顺序在\(T\)上排列在一条链上。
问对图\(G\)操作到不能操作时,\(|E_G|\)是多少。
\(n,m\le 2000\)
神仙题。。。对着三个标切的,下次遇到估计还是不会做。。。
如果\(a,b,c\)是以固定的顺序(即\(a,b,c\))排列在一条链上,就可以通过搜索来解决。
但是这里是以任意顺序,所以尝试调整:如果有\((a,c),(a,b)\in E_G\),且\(a,b,c\)按顺序排在\(T\)的一条链上,则将\((a,c)\)替换成\((b,c)\)。(题解把这个操作称作“缩短(shorten)”)。这样调整之后就可以按照上面的方式来搞。
记\(f_{u,v}\)表示如果出现了边\((u,v)\),就可以将其替换成\((f_{u,v},v)\)。一开始\(f_{u,v}=u\)。
现在考虑加入一条边\((u,v)\)。先用\(f\)来将\((u,v)\)缩短到不能再缩为止。在\(u\)为根的树中,\(v\)的子树内找到一点\(w\),如果\(f_{u,w}=u\),则\(f_{u,w}\leftarrow v\);否则加入边\((v,w)\)(考虑原来有边\((u,f_{u,w}),(f_{u,w},w)\),现在本来应该有边\((u,v),(v,w)\),这样形成的相对顺序大概是\(u\to v\to f_{u,w}\to w\),中间两个的顺序可以调换。这时候当然是要加边\((v,f_{u,w})\))。在\(v\)为根的子树中同理。
以上“加入”和操作的过程用队列实现。
最后统计时,枚举\(u\)作为根节点,从\(u\)开始dfs。记\(mid\)表示中转点,满足\(f_{u,mid}=mid\)(记存在调整后的边\((u,mid)\))。到节点\(v\)时如果\(f_{mid,v}=v\)则答案加一并且\(mid\leftarrow v\)(反证:如果存在一个中转点\(mid'\)深度比\(mid\)小,而且\(f_{mid',v}=v\and f_{mid,v}\neq v\),因为\(f_{mid',mid}=mid,f_{mid',v}=v\),所以应有\(f_{mid,v}=v\),矛盾)。然后继续往下搜。
时间复杂度的分析大概是说每个\(f_{u,v}\)只会变一次,所以是\(O(n^2+nm)\)。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 2005
int n,m;
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
int fa[N][N];
int rt,mid;
int f[N][N],bz[N][N];
queue<pair<int,int> > q;
void init(int x){
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[rt][x])
fa[rt][ei->to]=x,init(ei->to);
}
void dfs(int x){
f[rt][x]=mid;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[rt][x]){
if (f[rt][ei->to]==rt)
dfs(ei->to);
else
q.push(make_pair(mid,ei->to));
}
}
int ans;
void collect(int x,int mid){
if (x!=rt && f[mid][x]==x)
ans++,mid=x;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[rt][x])
collect(ei->to,mid);
}
int main(){
freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
e[ne]={v,last[u]};last[u]=e+ne++;
e[ne]={u,last[v]};last[v]=e+ne++;
}
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
f[i][j]=i;
for (int i=1;i<=n;++i)
rt=i,init(rt);
for (int i=1;i<=m;++i){
int u,v;
scanf("%d%d",&u,&v);
q.push(make_pair(u,v));
// printf("i=%d\n",i);
while (!q.empty()){
int u=q.front().first,v=q.front().second;
// printf("%d %d\n",u,v);
q.pop();
while (u!=v && f[u][v]!=u) u=f[u][v];
while (u!=v && f[v][u]!=v) v=f[v][u];
if (u==v) continue;
rt=u,mid=v,dfs(v);
rt=v,mid=u,dfs(u);
}
// printf("\n");
}
// for (int i=1;i<=n;++i)
// for (int j=1;j<=n;++j)
// if (i!=j && f[i][j]==j)
// printf("(%d,%d)\n",i,j);
for (int i=1;i<=n;++i)
rt=i,collect(i,i);
printf("%d\n",ans/2);
return 0;
}