一棵树(T)和一张图(G),现在对图进行加边操作:每次找到((a,b,c))满足((a,b),(b,c)in E_G),且(a,b,c)任意顺序在(T)上排列在一条链上。
问对图(G)操作到不能操作时,(|E_G|)是多少。
(n,mle 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 o v o f_{u,w} o w),中间两个的顺序可以调换。这时候当然是要加边((v,f_{u,w})))。在(v)为根的子树中同理。
以上“加入”和操作的过程用队列实现。
最后统计时,枚举(u)作为根节点,从(u)开始dfs。记(mid)表示中转点,满足(f_{u,mid}=mid)(记存在调整后的边((u,mid)))。到节点(v)时如果(f_{mid,v}=v)则答案加一并且(midleftarrow v)(反证:如果存在一个中转点(mid')深度比(mid)小,而且(f_{mid',v}=vand f_{mid,v} eq 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
",i);
while (!q.empty()){
int u=q.front().first,v=q.front().second;
// printf("%d %d
",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("
");
}
// for (int i=1;i<=n;++i)
// for (int j=1;j<=n;++j)
// if (i!=j && f[i][j]==j)
// printf("(%d,%d)
",i,j);
for (int i=1;i<=n;++i)
rt=i,collect(i,i);
printf("%d
",ans/2);
return 0;
}