一道LCA+树上差分
原题链接
显然每一条新增边都会导致环。
如果试着举些例子的话,很容易发现割掉非环上的边,则割掉其他任意一条新增边都可达成目标;若割掉的原有边是一个环上的边,那么只有割掉导致这个环出现的新增边才能使得图变为不连通的两部分。若割掉的边是两个或两个以上的环上的边,那么无论第二次怎么割都不可能达成目标。
也就是说,我们只要统计每一条原有边是几个环上的边,就可以直接枚举并计算方案个数。
可以直接通过树链剖分暴力计算,但这里我们可以用针对边的树上差分来解决,且跑的更快。
枚举每条新增边,设两端点为\(x,y\),则我们给节点\(x,y\)的权值加上\(1\),对节点\(LCA(x,y)\)的权值减去\(2\)。枚举完毕后用\(dfs\)求出\(f[x]\),表示以\(x\)为根的子树中所有节点的权值之和,而最后的\(f[x]\)实际上表示\(x\)与它父亲节点的这条边是几个环上的边,然后枚举所有原有边并统计答案即可。
\(LCA\)部分可以使用\(tarjan\),跑的更快些。
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
struct eg {
int x, y;
};
eg a[N];
struct dd {
int y, nex, id;
};
dd q[N << 1];
int fi[N], di[N << 1], ne[N << 1], f[N], fi_q[N], an[N], v[N], fa[N], l, lq;
inline int re()
{
int x = 0;
char c = getchar();
bool p = 0;
for (; c<'0' || c>'9'; c = getchar())
p |= c == '-';
for (; c >= '0'&&c <= '9'; c = getchar())
x = x * 10 + (c - '0');
return p ? -x : x;
}
inline int fin(int x)
{
if (!(fa[x] ^ x))
return x;
return fa[x] = fin(fa[x]);
}
inline void add(int x, int y)
{
di[++l] = y;
ne[l] = fi[x];
fi[x] = l;
}
inline void add_q(int x, int y, int z)
{
q[++lq].y = y;
q[lq].nex = fi_q[x];
q[lq].id = z;
fi_q[x] = lq;
}
void tarjan(int x)
{
int i, y;
v[x] = 1;
for (i = fi[x]; i; i = ne[i])
{
y = di[i];
if (!v[y])
{
tarjan(y);
fa[y] = x;
}
}
for (i = fi_q[x]; i; i = q[i].nex)
{
y = q[i].y;
if (!(v[y] ^ 2))
an[q[i].id] = fin(y);
}
v[x] = 2;
}
void dfs(int x, int fa)
{
int i, y;
for (i = fi[x]; i; i = ne[i])
{
y = di[i];
if (y != fa)
{
dfs(y, x);
f[x] += f[y];
}
}
}
int main()
{
int i, n, m, x, y, s = 0;
n = re();
m = re();
for (i = 1, fa[n] = n; i < n; i++)
{
fa[i] = i;
x = re();
y = re();
add(x, y);
add(y, x);
}
for (i = 1; i <= m; i++)
{
a[i].x = re();
a[i].y = re();
if (!(a[i].x^a[i].y))
an[i] = a[i].x;
add_q(a[i].x, a[i].y, i);
add_q(a[i].y, a[i].x, i);
}
tarjan(1);
for (i = 1; i <= m; i++)
{
f[a[i].x]++;
f[a[i].y]++;
f[an[i]] -= 2;
}
dfs(1, 0);
for (i = 2; i <= n; i++)
{
if (!f[i])
s += m;
else
if (!(f[i] ^ 1))
s++;
}
printf("%d", s);
return 0;
}