/** 并查集(DSU) * 2023-08-04: https://ac.nowcoder.com/acm/contest/view-submission?submissionId=63239142 (base on jiangly) * 2025-11-08: https://frp-shy.com:32768/domjudge/team H题 增加dsu on next,标记区间l,r的功能 * 为了适配这个dsu on next,对merge 函数合并方向进行了修改。 * 2025-12-05:https://codeforces.com/contest/2171/submission/351963922 * 增加了 countFa 功能 * 2025-12-23:移除了宏定义,增加功能说明模块。 * 功能说明:dsu on next,find(x),就是在 x 之后第一个没有被标记的数字 * 该 dsu 传入构造函数的这个大小,需要是 n 就是 n,如果你不希望 countFa 出错的话。 **/ structDSU { vector<int> fa, siz; ll n;
DSU() { }
DSU(ll n): n(n) { init(n + 5); }
voidinit(ll n){ fa.resize(n); for (int i = 0; i < SZ(fa); ++i) { fa[i] = i; } siz.assign(n, 1); }
ll find(ll x){ while (x != fa[x]) { x = fa[x] = fa[fa[x]]; } return x; }
boolsame(ll x, ll y){ returnfind(x) == find(y); }
boolmerge(ll x, ll y){ x = find(x); y = find(y); if (x == y) { returnfalse; } siz[y] += siz[x]; fa[x] = y; returntrue; }
ll size(ll x){ return siz[find(x)]; }
// dsu on next 技巧,直接标记 [l,r] 这段区间 // 标记就是指让[l,r]这段区间里的所有数字这个都不指向自己 // 可以理解为指向 r+1 voidtaglr(ll l, ll r){ l = max(0ll, l); r = min(SZ(fa) - 2, r); l = find(l); while (l <= r) { merge(l, l + 1); l += 1; l = find(l); } }
ll countFa(){ ll res = 0; for (int i = 1; i <= n; ++i) { if (fa[i] == i) { ++res; } } return res; } };
// 在 k<=0 的时候返回 1 ll binpow(ll a, ll k, const ll &p = mod){ ll res = 1; a %= p; while (k > 0) { if ((k & 1) == 1) res = a * res % p; a = a * a % p; k >>= 1; } return res; }
voidSolve(){ ll N, M; cin >> N >> M; vector<vector<ll> > g(N + 2); DSU fa(N); DSU without_edge_to_n_fa(N); map<ll, map<ll, bool> > u_to_v_mp; for (int i = 1; i <= M; ++i) { ll u, v; cin >> u >> v; u_to_v_mp[u][v] = true; u_to_v_mp[v][u] = true; // g[u].push_back(v); // g[v].push_back(u); fa.merge(u, v); if (u != N && v != N) { without_edge_to_n_fa.merge(u, v); } } ll mx_node_connect_to_n = 0; for (auto it: u_to_v_mp[N]) { mx_node_connect_to_n = max(mx_node_connect_to_n, it.first); } bool n_to_n_1 = u_to_v_mp[N].contains(N - 1); ll s_prod = 1; vector<char> vis_connect(N + 2); for (int i = 1; i <= N; ++i) { ll pa = fa.find(i); if (vis_connect[pa]) { continue; } vis_connect[pa] = true; s_prod *= fa.size(i); s_prod %= mod; } ll k_cntFa = fa.countFa(); auto cal_ans = [&](ll k_rem_connect, vector<ll> divide_ls, bool iscal_sum = false)-> ll { if (k_rem_connect <= 1) return1; if (iscal_sum) { // 计算总单答案,直接使用该式子计算即可 ll res = s_prod * binpow(N, k_rem_connect - 2); res %= mod; return res; } ll multiple = accumulate(all(divide_ls), 0ll); ll op_s_prod = s_prod; for (auto divide: divide_ls) { op_s_prod *= binpow(divide, mod - 2); op_s_prod %= mod; } op_s_prod *= multiple; op_s_prod %= mod; ll res = binpow(N, k_rem_connect - 2) * op_s_prod % mod; if (res < 0) res += mod; return res; }; // 我们下面再开一个答案计算函数,有点屎山,但是也没办法 auto cal_ans2 = [&](ll k_rem_connect, vector<ll> divide_ls,ll sz_i_subtree, ll sz_n_component)-> ll {
if (k_rem_connect <= 1) return1; ll multiple = accumulate(all(divide_ls), 0ll); ll op_s_prod = s_prod; for (auto divide: divide_ls) { op_s_prod *= binpow(divide, mod - 2); op_s_prod %= mod; } op_s_prod *= multiple; op_s_prod %= mod; ll res = binpow(N, k_rem_connect - 2) * op_s_prod % mod; res*=sz_i_subtree;res%=mod; res*=binpow(sz_n_component,mod-2);res%=mod; if (res < 0) res += mod; return res; }; for (int i = 1; i <= N - 1; ++i) { if (i == N - 1) { if (n_to_n_1) { // 总答案这样子计算有问题 // ll ans=binpow(N,k_cntFa-2)*s_prod; ll ans = cal_ans(k_cntFa, {}, true); cout << ans << " "; continue; } if (fa.same(N, N - 1)) { cout << 0 << " "; continue; } ll ans = cal_ans(k_cntFa - 1, {fa.size(i), fa.size(N)}); cout << ans << " "; } else { if (n_to_n_1) { cout << 0 << " "; continue; } // 处于同一连通块下 if (fa.same(N, i)) { if (u_to_v_mp[N].contains(i)) { if (fa.same(N - 1, N)) { // N-1 已经在 i 的子树上,已经稳了,剩下的点随便怎么连接 if (without_edge_to_n_fa.same(N - 1, i)) { // 就是直接计算总答案 ll ans = cal_ans(k_cntFa, {}, true); cout << ans << " "; } else { // N-1 在 N 连通块,但是不在 i 的子树上,直接宣判死刑 cout << 0 << " "; } } else { // N 和 i 直接相连 // 需要把 N-1 所在联通块连上 i 的子树 // 总共答案就是把这个 i 和 N-1连上以后的总答案,乘以 i 的子树大小 ll ans = cal_ans2(k_cntFa,{1},without_edge_to_n_fa.size(i),fa.size(N)); ans %= mod; cout << ans << " "; } } else { // i 和 N 处于同一连通块的时候而且未直接相连,答案为 0。 cout << 0 << " "; continue; } } else { if (fa.same(N, N - 1)) { cout << 0 << " "; continue; } ll ans; // 这里这个分类讨论,最开始的程序漏了 if (fa.same(N-1,i)) { ans=cal_ans(k_cntFa-1,{fa.size(i),fa.size(N)}); }else { // 我们现在必须要把这个 N-1连接到这个 i 上面(即 i 的子树,连通块上面) ans = cal_ans2(k_cntFa - 1, {fa.size(i),fa.size(N)}, fa.size(i),fa.size(i)+fa.size(N)); } cout << ans << " "; } } } cout << "\n"; }