原题来自:POJ 3417
Dark 是一张无向图,图中有 NN 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark 有 N - 1N−1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 MM 条附加边。
你的任务是把 Dark 斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断 Dark 的一条附加边。
现在你想要知道,一共有多少种方案可以击败 Dark。注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。
输入格式
第一行包含两个整数 NN 和 MM;
之后 N - 1N−1 行,每行包括两个整数 AA 和 BB,表示 AA 和 BB 之间有一条主要边;
之后 MM 行以同样的格式给出附加边。
输出格式
输出一个整数表示答案。
样例
Inputcopy | Outputcopy |
---|---|
4 1 1 2 2 3 1 4 3 4 | 3 |
数据范围与提示
对于 20\%20% 的数据,1\le N,M\le 1001≤N,M≤100;
对于 100\%100% 的数据,1\le N\le 10^5,1\le M\le 2\times 10^51≤N≤105,1≤M≤2×105。数据保证答案不超过 2^{31}-1231−1。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+4;
int f[N][30],depth[N],cnt;
int num[N],fx[N],fy[N],ff[N];
struct dd
{
int nxt,to;
} e[N];
int h[N];
void add(int u,int v)
{
e[++cnt].nxt=h[u];
h[u]=cnt;
e[cnt].to=v;
}
void dfs(int x)
{
memset(depth,-1,sizeof(depth));
depth[x]=1;// depth[x]记录x的深度
depth[0]=0;
queue<int>q;
q.push(x);
while(!q.empty())
{
int t=q.front();
q.pop();
for(int i=h[t]; i; i=e[i].nxt)
{
int j=e[i].to;
if(depth[j]==-1)
{
depth[j]=depth[t]+1;
q.push(j);
f[j][0]=t; //2的0次方=1,即fy是j的父亲
for(int k=1; k<=19; k++)
f[j][k]=f[f[j][k-1]][k-1];// j的k次方 == j的 k-1次方 + j的 k-1次方
}
}
}
}
int lca(int x,int y)
{
if(depth[x]<depth[y]) swap(x,y);
for(int i=20; i>=0; i--)
if(depth[f[x][i]]>=depth[y])
x=f[x][i]; //往上寻找lca
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];
}
void getf(int now,int fa)
{
ff[now]+=num[now]; //ff[x]就是x与它的父节点之间的“树边”被附加边覆盖的次数
for(int i=h[now]; i; i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
getf(v,now);
ff[now]+=ff[v];
}
}
int main()
{
int n,m,ans=0,x,y;
cin>>n>>m;
for(int i=1; i<n; i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1);
for(int i=1; i<=m; i++)
{
scanf("%d%d",&fx[i],&fy[i]);
num[fx[i]]++; //差分
num[fy[i]]++;
num[lca(fx[i],fy[i])]-=2;
}
getf(1,0); //
for(int i=2; i<=n; i++)
{
if(ff[i]==0) ans+=m;//第一次把覆盖零次的边切掉,第二次可以从 m条附着边中随意选一条进行切掉
if(ff[i]==1) ans++;//第一次把覆盖一次的边切掉,第二次只能切这一条附加边边
}
cout<<ans;
return 0;
}