P3993 [BJOI2017] 同构 题解

Description

你有 nn 个点,你可以在这 nn 个点之间连无向边,两个点之间至多只能连一条边,也不允许连自环,问至多能连多少条边。

但这个问题的答案显然是 n(n1)2\frac{n(n-1)}{2} 条。所以有一个额外的限制,要求这个图不存在非平凡的自同构。

一个图 GG 有非平凡的自同构定义为存在一个 11nn 的置换 p(1)p(1)p(n)p(n) 满足对于所有点 u,vu,v(u,v)(u, v) 之间有边当且仅当 (p(u),p(v))(p(u), p(v)) 之间有边,并且这个置换非平凡也就是存在一个点 uu 使得 p(u)up(u) \ne u

比如对于一个 55 个点的图 (1,2),(2,3),(3,4),(4,5),(5,1),(1,3)(1,2),(2,3),(3,4),(4,5),(5,1),(1,3),那么 p(1)=3p(1)=3p(2)=2p(2)=2p(3)=1p(3)=1p(4)=5p(4)=5p(5)=4p(5)=4 为这个图的一个非平凡的自同构。

你要回答一个 nn 个点的无向简单的不存在非平凡自同构的图最多有多少条边,如果答案不存在,即不存在 nn 个点满足条件的图,请输出 -1,否则输出答案对 109+710^9+7 取模的结果。

对于 100%100\% 的数据,1n101001 \le n \le 10^{100}1T1041 \le T \le 10^4

Solution

观察样例可以得到 n6n\leq 6 的答案,这里只考虑 n7n\geq 7 的情况。

容易发现对于一个图 GG 合法,则 GG 的补图 GG' 也合法,所以只需要求出最少有多少条边即可。

手玩之后会发现对于每个合法方案,每个连通块一定不存在非平凡自同构且连通块两两不同构。

首先对于 n7n\geq 7 一定有解,构造如下:

容易发现对于每个 nn,最小的情况一定是个森林,证明就考虑如果只有 1\leq 1 个树,则把方案改成一个 nn 个点的树一定不劣。然后这时就有 2\geq 2 个树了,则让点数最多的树和其它非树连通块的点删掉原先的边并连成一颗新树,这样一定合法且不劣。

所以只需要让树的个数最多即可。

容易发现肯定是先取点数小的树再取点数大的,剩下的点合并到最大的树一定合法。

考虑什么样的树是合法的。先固定根,对于每个点 xx 的所有子树,一定是不同构的,如果对于所有的根都满足这个条件就一定合法。

有一个结论是:在判断树同构的时候,我们可以以重心为根做一遍树哈希(如果有两个就都做一遍),然后比较哈希值判断两棵树是否同构。

于是可以用重心来计算方案数可以避免算重。

fnf_n 表示 nn 个点的无根树的个数,gn,mg_{n,m} 表示由大小 n\leq n 的树组成大小为 mm 的森林的方案数,hnh_n 表示 nn 个点的有根树个数。

则:

hn=gn1,n1gn,m=i=0mn(hni)gn1,minfn=gn12,n1+[2n](hn22)\begin{aligned} h_n&=g_{n-1,n-1}\\ g_{n,m}&=\sum_{i=0}^{\left\lfloor\frac{m}{n}\right\rfloor}\binom{h_n}{i}g_{n-1,m-in}\\ f_n&=g_{\left\lfloor\frac{n-1}{2}\right\rfloor,n-1}+\left[2|n\right]\binom{h_{\frac{n}{2}}}{2} \end{aligned}

经计算,当 n=266n=266ifi\sum{if_i} 已经 10100\geq 10^{100} 了。

时间复杂度:O(玄学)O(玄学)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#include <bits/stdc++.h>

#define int int64_t

namespace BIGINT {
using ull = unsigned long long;
using bigint = __int128;
using bigbig = __int128;
struct bign {
static const int block = 16;
static const ull base = 10000000000000000;
std::vector<ull> a;
bign() : a(std::vector<ull>()) {}
bign(ull x) {
for (; x; x /= base) a.push_back(x % base);
}
bign(std::string s) {
std::reverse(s.begin(), s.end());
for (int i = 0; i < (int)s.length(); i += block) {
int r = std::min((int)s.length(), i + block);
ull x = 0;
for (int j = r - 1; j >= i; j--) x = x * 10 + s[j] - 48;
a.push_back(x);
}
}
bign operator=(ull x) { return *this = bign(x); }
bign operator=(std::string s) { return *this = bign(s); }
void resize(int len) { a.assign(len, 0); }
ull operator[](const int &x) const { return a[x]; }
ull &operator[](const int &x) { return a[x]; }
friend std::istream &operator>>(std::istream &in, bign &x) {
std::string s;
in >> s, x = s;
return in;
}
friend std::ostream &operator<<(std::ostream &out, const bign &x) {
if (x.a.empty())
out << "0";
else {
ull ed = x.a.back();
printf("%llu", ed);
for (int i = x.a.size() - 2; ~i; i--) printf("%0*llu", block, x[i]);
}
return out;
}
bool operator<(const bign &x) const {
if (a.size() != x.a.size()) return a.size() < x.a.size();
for (int i = a.size() - 1; ~i; i--)
if (a[i] ^ x[i]) return a[i] < x[i];
return 0;
}
bool operator==(const bign &x) const {
if (a.size() != x.a.size()) return 0;
for (int i = 0; i < (int)a.size(); i++)
if (a[i] ^ x[i]) return 0;
return 1;
}
bool operator!=(const bign &x) const { return !(*this == x); }
bool operator>(const bign &x) const { return x < *this; }
bool operator<=(const bign &x) const {
if (a.size() != x.a.size()) return a.size() < x.a.size();
for (int i = a.size() - 1; ~i; i--)
if (a[i] ^ x[i]) return a[i] < x[i];
return 1;
}
bool operator>=(const bign &x) const { return x <= *this; }
bign operator+=(const bign &x) {
ull r = 0;
for (int i = 0; i < (int)x.a.size() || r; i++) {
if (i < (int)x.a.size()) r += x[i];
if (i >= (int)a.size()) a.push_back(0);
if ((a[i] += r) >= base)
r = 1, a[i] -= base;
else
r = 0;
}
return *this;
}
bign operator+(const bign &x) const {
bign t = *this;
return t += x;
}
bign operator-=(const bign &x) {
ull r = 0;
for (int i = 0; i < (int)x.a.size() || r; i++) {
if (i < (int)x.a.size()) r += x[i];
if (a[i] >= r)
a[i] -= r, r = 0;
else
a[i] += base - r, r = 1;
}
for (; !a.empty() && !a.back();) a.pop_back();
return *this;
}
bign operator-(const bign &x) const {
bign t = *this;
return t -= x;
}
bign operator-=(const ull &_x) {
ull r = 0;
ull x = _x;
for (int i = 0; x || r; i++) {
r += x % base, x /= base;
if (a[i] >= r)
a[i] -= r, r = 0;
else
a[i] += base - r, r = 1;
}
for (; !a.empty() && !a.back();) a.pop_back();
return *this;
}
bign operator-(const ull &x) const {
bign t = *this;
return t -= x;
}
friend void reduce(bign &a) {
for (; !a.a.empty() && !a.a.back(); a.a.pop_back());
}
friend void split(const bign &a, bign &x, bign &y, int mid) {
int len = std::min(mid, (int)a.a.size());
y.resize(len);
for (int i = 0; i < len; i++) y[i] = a[i];
len = std::max<int>(0, (int)a.a.size() - mid);
x.resize(len);
for (int i = 0; i < len; i++) x[i] = a[mid + i];
reduce(x), reduce(y);
}
friend bign mul(const bign &a, int x) {
if (a.a.empty()) return bign();
bign b;
b.resize(a.a.size() + x);
for (int i = a.a.size() - 1; ~i; i--) b[i + x] = a[i];
return b;
}
bign operator*(const bign &x) const {
if (a.size() <= 20 && x.a.size() <= 20) {
int len = a.size() + x.a.size() + 1;
std::vector<bigbig> t(a.size() + x.a.size() + 1);
for (int i = 0; i < (int)a.size(); i++)
for (int j = 0; j < (int)x.a.size(); j++) {
int k = i + j;
t[k] += (bigbig)a[i] * x[j], t[k + 1] += t[k] / base, t[k] %= base;
}
bign ans;
ans.resize(len);
for (int i = 0; i < (int)t.size(); i++) {
if (i + 1 < (int)t.size()) t[i + 1] += t[i] / base;
ans[i] = t[i] % base;
}
reduce(ans);
return ans;
}
int mid = (std::max(a.size(), x.a.size()) + 1) / 2;
bign A, B, C, D;
split(*this, A, B, mid);
split(x, C, D, mid);
bign ac = A * C, bd = B * D, t = (A + B) * (C + D) - ac - bd;
return mul(ac, mid * 2) + mul(t, mid) + bd;
}
bign operator*=(const bign &x) { return *this = *this * x; }
bign operator*=(const int &x) {
bigint r = 0;
for (int i = 0; i < (int)a.size() || r; i++) {
if (i >= (int)a.size()) a.push_back(0);
r += x * (bigint)a[i], a[i] = r % base, r /= base;
}
return *this;
}
bign operator*(const int &x) const {
bign t = *this;
return t *= x;
}
bign operator*=(const ull &x) {
bigint r = 0;
for (int i = 0; i < (int)a.size() || r; i++) {
if (i >= (int)a.size()) a.push_back(0);
r += x * (bigint)a[i], a[i] = r % base, r /= base;
}
return *this;
}
bign operator*(const ull &x) const {
bign t = *this;
return t *= x;
}
bign operator/=(const ull &x) {
bigint r = 0;
for (int i = a.size() - 1; ~i; i--) {
r = r * base + a[i];
a[i] = r / x, r %= x;
}
for (; !a.empty() && !a.back();) a.pop_back();
return *this;
}
bign operator/(const ull &x) const {
bign t = *this;
return t /= x;
}
friend bign qpow(const bign &_x, const ull &_y) {
bign x(_x), ans(1);
for (ull y = _y; y; y >>= 1, x *= x)
if (y & 1) ans *= x;
return ans;
}
ull trans() const {
ull x = 0;
for (int i = a.size() - 1; ~i; i--) x = x * base + a[i];
return x;
}
ull operator%(const ull &x) const {
ull res = 0;
for (int i = a.size() - 1; ~i; i--) {
res = ((bigbig)res * base + a[i]) % x;
}
return res;
}
};
} // namespace BIGINT

using BIGINT::bign;

using i128 = __int128_t;

const int kMaxN = 270, kMod = 1e9 + 7;

bign f[kMaxN], g[kMaxN][kMaxN], h[kMaxN];

constexpr int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1) ret = (int64_t)ret * bs % kMod;
return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

bign C(bign n, int m) {
static bool vis[kMaxN];
if (n < m) return 0;
bign ret = 1;
memset(vis, 0, sizeof(vis));
for (bign i = n; i > n - m; i = i - 1) {
ret *= i;
for (int j = 1; j <= m; ++j)
if (!vis[j] && ret % j == 0) ret /= j, vis[j] = 1;
}
return ret;
}

void prework() {
g[0][0] = 1;
for (int i = 1; i <= 266; ++i) {
h[i] = g[i - 1][i - 1];
if (i & 1)
f[i] = g[(i - 1) / 2][i - 1];
else
f[i] = g[i / 2 - 1][i - 1] + C(h[i / 2], 2);
for (int j = 0; j <= 266; ++j) {
for (int k = 0; k <= j / i; ++k)
g[i][j] += C(h[i], k) * g[i - 1][j - i * k];
}
}
}

int solve(bign n) {
if (n == 1)
return 0;
else if (n <= 5)
return -1;
else if (n == 6)
return 9;
int _n = n % kMod, ans = (1ll * _n * (_n - 1) / 2 % kMod - _n + kMod) % kMod;
for (int i = 1; i <= 266; ++i) {
if (n > (bign)i * f[i]) {
n -= (bign)i * f[i], inc(ans, f[i] % kMod);
} else {
inc(ans, (n / i) % kMod);
break;
}
}
return ans;
}

void dickdreamer() {
bign n;
std::cin >> n;
std::cout << solve(n) << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T = 1;
std::cin >> T;
prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P3993 [BJOI2017] 同构 题解
https://sobaliuziao.github.io/2024/07/10/post/719f3de5.html
作者
Egg_laying_master
发布于
2024年7月10日
许可协议