一棵树\(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;
}