9 收心赛1 T2 深度优先搜索树
深度优先搜索树
题面
给定 \(dfs\) 代码
void dfs(int u) {
vis[u] = true;
for (int v = 1; v <= n; v++)
if (g[u][v] == true && vis[v] == false)
dfs(v), link(u, v);
}
给定一个 \(n\) 个点的树,问有多少个没有重边和自环的图 \(G\) 满足执行 \(dfs(1)\) 后生成的树和给定的树相同?
\(link\) 表示连边,\([g(u,v)]\) 表示 \(u,v\) 之间有无向边
\(n \le 2 \times 10^5\)
题解
感觉这道题其实思路上比上个题还简单些,考场上写挂了是因为没有考虑到 \(dfs\) 在一条链的情况下会退化成 \(O(n^2)\) ,后面要注意
好了,看完题手模几个小样例应该大概知道怎么连边
原树上的边一定要有,考虑其他的边

假设两个点没有祖先关系(如 4和3),那么能不能连边?不能,因为如果连边,那么就不会遍历原有的树边,而是顺着新连的边 dfs 到 3,这样显然是不符合题意的
那么考虑有祖先关系的两个点如何连边?
如上图中的 6,7 连边可以吗,模拟发现不行,因为如果的靠下的节点(6)比靠上节点的子节点编号小,那么就会破坏原来的遍历顺序
所以两个点可以连边,当且仅当
两个点满足祖先关系并且靠下点编号大于靠上点子节点的编号
考场上我的思路是统计每个子树内根节点可以向那些节点连边,但是不好做(mtx用权值线段树+合并写出来了\bx),而且时间复杂度比较高
正难则反,考虑对于一个下面的节点,我们统计那些节点可以向它连边
但是直接去找那个靠上的点好像不太好找,我们发现连边条件和靠上节点的编号没有关系,所以我们可以转而去统计那个子节点,这个子节点要满足编号小于当前节点,并且不能是根节点(要有父亲)
可以用树状数组统计这个点到根的路径上的点的分布情况,然后 \(\log n\) 查询即可
每次时间复杂度为 \(O(\log n)\) 总时间复杂度为 \(O(n \log n)\)
code
代码比较简单,注意答案可能爆 \(int\)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int n;
int t[N];
int h[N], ver[N << 1], ne[N << 1], tot;
ll ans = 0;
void add (int x, int y) {
ver[++tot] = y;
ne[tot] = h[x];
h[x] = tot;
}
int pw (int a, ll b) {
ll res = 1, t = a;
while (b) {
if (b & 1) {
res *= t;
res %= mod;
}
t *= t;
t %= mod;
b >>= 1;
}
return res;
}
void Add (int x, int d) {
while (x < N) {
t[x] += d;
x += x & -x;
}
}
int ask (int x) {
int res = 0;
while (x) {
res += t[x];
x -= x & -x;
}
return res;
}
void dfs (int x, int fa) {
Add (x, 1);
//这里 -1 是因为根节点 1 一定会被统计上,而它必定没有父节点,所以不能作为
if (fa) ans += ask (x - 1) - 1;
for (int i = h[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa) continue;
dfs (y, x);
}
Add (x, -1);
}
int main () {
freopen ("dfs.in", "r", stdin);
freopen ("dfs.out", "w", stdout);
cin >> n;
for (int i = 1; i < n; i++) {
int x, y;
scanf ("%d%d", &x, &y);
add (x, y);
add (y, x);
}
dfs (1, 0);
cout << pw (2, ans) << endl;
return 0;
}

浙公网安备 33010602011771号