luogu4337 && bzoj5211 线图
分析
可怜日常劝退题。
好久没有写这么硬核的题了。
手玩:人类的智慧
这道题的重点就在于手玩,玩着玩着才能玩出一些名堂来。
比如这张图。
L
(
G
)
L(G)
L(G)
没啥可说的。
L
2
(
G
)
L^2(G)
L2(G)
观察二阶的图,发现图中的每一个点
稍微重标号一下下
(
1
,
2
,
3
)
−
>
1
,
(
2
,
3
,
4
)
−
>
2
,
(
1
,
2
,
4
)
−
>
3
,
(
2
,
4
,
5
)
−
>
4
(1,2,3)->1, (2,3,4)->2,(1,2,4)->3,(2,4,5)->4
(1,2,3)−>1,(2,3,4)−>2,(1,2,4)−>3,(2,4,5)−>4
L
3
L3
L3
首先从一次变换来看:
1.点数变成边数。
2.
G
G
G一条边
(
u
,
v
)
(u,v)
(u,v)的在
L
(
G
)
L(G)
L(G)的度数为
d
u
+
d
v
−
2
d_u+d_v-2
du+dv−2
暂时,也只能挖掘这么多的性质。
从骗分的角度来看;
L
(
G
)
:
L(G):
L(G):输出边数
L
2
(
G
)
:
L^2(G):
L2(G):每一个“弯折(
<
u
,
v
>
,
<
v
,
w
>
<u,v>,<v,w>
<u,v>,<v,w>)”在
L
(
G
)
L(G)
L(G)中都是一条边。每个点
u
u
u可以有
C
d
u
2
C_{d_u}^2
Cdu2种“弯折”,所以
A
n
s
(
L
2
(
G
)
)
=
∑
C
d
u
2
Ans(L^2(G))=\sum C_{d_u}^2
Ans(L2(G))=∑Cdu2
L
3
(
G
)
:
L^3(G):
L3(G):看成
L
2
(
L
(
G
)
)
L^2(L(G))
L2(L(G)),
L
(
G
)
L(G)
L(G)的每个点的度数是
d
u
+
d
v
−
2
d_u+d_v-2
du+dv−2
所以
A
n
s
(
L
3
(
G
)
)
=
∑
m
C
d
u
+
d
v
−
2
2
Ans(L^3(G))=\sum_mC_{d_u+d_v-2}^2
Ans(L3(G))=∑mCdu+dv−22
L
4
(
G
)
:
L^4(G):
L4(G):问题有些棘手,仍然将其看成
L
3
(
L
(
G
)
)
L^3(L(G))
L3(L(G)),不过我们不能再仅仅靠度数了,我们得挖掘一下图的形态。
首先换一个角度考虑
L
3
(
G
)
L^3(G)
L3(G)
我们发现,
L
3
(
G
)
L^3(G)
L3(G)的点数实际上是不同的三条边的联通子图个数(边有标号)。
那么一共有环、链和鸡爪三种形态,注意选边的顺序是有影响的。
鸡爪:
∑
3
C
d
u
3
\sum 3C_{d_u}^3
∑3Cdu3
环和链:
∑
(
u
,
v
)
(
d
u
−
1
)
(
d
v
−
1
)
\sum_{(u,v)} (d_u-1)(d_v-1)
∑(u,v)(du−1)(dv−1)
再考虑
G
→
L
(
G
)
G\to L(G)
G→L(G)
原图上的某个点
u
u
u和围着它的一圈边。这一圈边在
L
(
G
)
L(G)
L(G)上构成了一张完全导出子图,而反过来说,整张图就是由这一张张导出子图构成的。
因此计算
L
(
G
)
L(G)
L(G)的
(
d
u
−
1
)
(
d
v
−
1
)
(d_u-1)(d_v-1)
(du−1)(dv−1)贡献时,任意一个完全子图的边两两匹配,每个点
u
u
u的贡献就是
1
2
(
(
∑
v
d
u
+
d
v
−
2
)
2
−
∑
(
u
,
v
)
(
d
u
+
d
v
−
2
)
2
)
\frac{1}{2}((\sum_v d_u+d_v-2)^2-\sum_{(u,v)}(d_u+d_v-2)^2)
21((∑vdu+dv−2)2−∑(u,v)(du+dv−2)2)
令
d
1
(
u
,
v
)
=
d
u
+
d
v
−
1
,
d
2
(
u
)
=
∑
d
1
(
u
,
v
)
−
1
d_1(u,v)=d_u+d_v-1,d_2(u)=\sum d_1(u,v)-1
d1(u,v)=du+dv−1,d2(u)=∑d1(u,v)−1
那么总贡献
1
2
∑
u
d
2
(
u
)
2
−
∑
(
u
,
v
)
(
d
1
(
u
,
v
)
)
\frac{1}{2}\sum_u d_2(u)^2-\sum_{(u,v)}(d_1(u,v))
21∑ud2(u)2−∑(u,v)(d1(u,v))
后面的
1
2
\frac{1}{2}
21不见了是因为每条边在两个点处各被减去了一次。
然后剩下的
∑
3
C
d
u
3
=
∑
(
u
,
v
)
3
C
d
1
(
u
,
v
)
3
\sum 3C_{d_u}^3=\sum_{(u,v)}3C_{d_1(u,v)}^3
∑3Cdu3=∑(u,v)3Cd1(u,v)3
加起来就好啦。
分析:问题的转化
至于更大的
K
K
K,人类智慧就不太够用了。所以只能慢慢用
L
L
L展开,大概可以得60pts+?
其实在推导的时候已经有了一些眉目,我们发现,
L
2
(
G
)
L^2(G)
L2(G)中的点数和”弯折“对应,
L
3
(
G
)
L^3(G)
L3(G)中的点数和三条边的联通子图对应,那么
L
k
(
G
)
L^{k}(G)
Lk(G)中的点数和不超过k条边的联通子图一一对应
考虑暴力搜索出所有点数的不超过
k
+
1
k+1
k+1的有根树(k=9的时候只有1205种),用这些有根树去匹配原树。
假设有
T
T
T棵树那么答案就是
∑
T
w
i
t
i
\sum_Tw_it_i
∑Twiti
w
i
w_i
wi是这棵树变换之后的点数,
t
i
t_i
ti是这棵树在原树中的出现次数。
w
i
w_i
wi用人类智慧+暴力变换可以搞出来。复杂度
O
(
能
过
)
O(能过)
O(能过)
t
i
t_i
ti实际上是一个有根树的匹配问题,采用状态压缩Dp
状态压缩Dp
f
i
,
j
f_{i,j}
fi,j表示原树上的
i
i
i子树匹配
T
T
T的
j
j
j子树的方案数。
方程。
考虑子树合并,合并的时候用一个
g
S
g_S
gS数组表示
j
j
j子树的儿子们的匹配情况为
S
S
S的方案数,枚举当前节点用哪一个子树匹配。
g
S
=
∑
g
S
−
p
f
s
o
n
,
p
g_S=\sum g_{S-p}f_{son,p}
gS=∑gS−pfson,p
然后
f
’
i
,
j
=
g
a
l
l
f’_{i,j}=g_{all}
f’i,j=gall
这样其实还有一点问题。
有根树和有标号的树是不等价的,但是在状态压缩Dp的时候我们强行给儿子标号了。但实际上,如果两个儿子是本质相同的,匹配的相对顺序是不打紧的。具体地来说,如果有
x
x
x组本质相同的儿子子树,每组的个数分别是
c
n
t
1
,
c
n
t
2
⋯
c
n
t
x
cnt_1,cnt_2\cdots cnt_x
cnt1,cnt2⋯cntx,那么我们重复计数了
∏
i
x
c
n
t
i
!
\prod_i^x cnt_i!
∏ixcnti!次。
在树
H
a
s
h
Hash
Hash处理出这个
∏
i
x
c
n
t
i
!
\prod_i^x cnt_i!
∏ixcnti!,然后计算完之后逆元乘上去即可。
有一个小小的常数优化,我们可以先挖掉所有的叶子节点,最后再把叶子节点放上去匹配。假设原树的当前儿子有
s
o
n
son
son个,
T
T
T子树的非叶子儿子有
p
p
p个,叶子节点有
l
f
lf
lf个,那么贡献就是
C
s
o
n
−
p
l
f
l
f
!
C_{son-p}^{lf} lf!
Cson−plflf!
乘上那个
l
f
lf
lf阶乘是因为状态压缩Dp的部分是有标号儿子,之后转成无标号的操作在
∏
i
x
c
n
t
i
!
\prod_i^x cnt_i!
∏ixcnti!一步中除掉了。两部分分开写会比较清晰。
代码
真的硬核,肝了半天。
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp std::make_pair
const int N = 5e3 + 10, M = 1e5 + 10, P = 998244353;
typedef std::pair<int, int>pa;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int fix(int x) {return (x >> 31 & P) + x;}
int add(int a, int b) {return a += b, a >= P ? a - P : a;}
int mul(int a, int b) {return 1LL * a * b % P;}
int Pow(int x, int k) {
int r = 1;
for(;k; x = mul(x, x), k >>= 1)
if(k & 1)
r = mul(r, x);
return r;
}
int bin[21], Lg[2049], cnt[2049], A[1 << 21], g[1 << 21], C[N][21];
int f[N][15], pr[N], to[N << 1], nx[N << 1], fac[21], tp, K, Ans, n, k;
void ins(int u, int v) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp;}
void adds(int u, int v) {ins(u, v); ins(v, u);}
std::vector<pa>E;
struct Tree {
int p[15], fa[15], lf[15], sz[15], same[15], st[30], k; bool all;
std::vector<int>T[15];
void Ins(int u, int f) {
p[f] |= bin[u];
fa[u] = f;
E.pb(mp(u, f));
T[u].pb(f);
T[f].pb(u);
}
void clear() {
for(int i = 0;i < k; ++i)
lf[i] = p[i] = 0, T[i].clear();
E.clear();
}
int Build() {
int u = 0, cnt = 0;
for(int i = 1;i <= k - 1 << 1; ++i)
if(!st[i]) {
if(!u) return -1;
u = fa[u];
}
else {
Ins(++cnt, u);
u = cnt;
if(u >= k) return -1;
}
return cnt + 1;
}
std::string Uni(int u, int t, int fa) {
if(all) sz[u] = 1;
std::vector<std::string>x;
for(int v : T[u])
if((bin[v] & t) && v != fa) {
x.pb(Uni(v, t, u));
if(all) sz[u] += sz[v];
}
std::sort(x.begin(), x.end());
std::string res = "";
for(int i = 0;i < x.size(); ++i)
res += '1' + x[i] + '0';
if(all) {
int cnt = 1; same[u] = 1;
for(int i = 1;i < x.size(); ++i)
if(x[i] == x[i - 1])
++cnt;
else
same[u] = mul(same[u], fac[cnt]), cnt = 1;
same[u] = Pow(mul(same[u], fac[cnt]), P - 2);
}
return res;
}
int Hash(std::string A) {
int res = 0;
for(int i = 0;i < A.size(); ++i)
if(A[i] == '1')
res |= bin[i];
return res;
}
int Sub(int t) {
std::string A = Uni(Lg[t&-t], t, -1);
if(A.size() != cnt[t] - 1 << 1)
return -1;
return Hash(A);
}
void Dp(int u, int fa) {
int son = 0;
for(int i = pr[u]; i; i = nx[i])
if(to[i] != fa)
Dp(to[i], u), ++son;
for(int rt = 0; rt < k; ++rt) {
if(sz[rt] == 1) continue;
if(son < cnt[p[rt]]) {
f[u][rt] = 0;
continue;
}
for(int k = p[rt]; k; k = (k - 1) & p[rt])
g[k] = 0;
g[0] = 1;
for(int i = pr[u];i; i = nx[i]) {
if(to[i] == fa) continue;
for(int t = p[rt]; t; t = (t - 1) & p[rt])
for(int u = t, lb; u; u ^= lb)
lb = u&-u,
g[t] = add(g[t], mul(g[t ^ lb], f[to[i]][Lg[lb]]));
}
f[u][rt] = mul(mul(g[p[rt]], same[rt]), C[son - cnt[p[rt]]][lf[rt]]);
}
}
int Calct() {
for(int i = 0;i < k; ++i)
for(int t = p[i], lb; t; t ^= lb)
if(sz[Lg[lb = t&-t]] == 1)
++lf[i], p[i] ^= lb;
Dp(1, 0); int ans = 0;
for(int i = 1;i <= n; ++i)
ans = add(ans, f[i][0]);
return ans;
}
}nw;
namespace Calcw {
int d[M], d2[M]; std::vector<int>B[M];
int c2(int x) {return (1LL * x * (x - 1) >> 1) % P;}
int c3(int x) {return mul(mul(x, x - 1), x - 2);}
int sqr(int x) {return mul(x, x);}
int Work(int n, int k) {
int res = 0;
if(k == 1) return E.size();
for(int i = 0;i < n; ++i)
d[i] = d2[i] = 0;
for(pa p : E)
++d[p.fi], ++d[p.se];
if(k == 2) {
for(int i = 0;i < n; ++i)
res = add(res, c2(d[i]));
return res;
}
if(k == 3) {
for(pa p : E)
res = add(res, c2(d[p.fi] + d[p.se] - 2));
return res;
}
if(k == 4) {
for(int i = 0;i < E.size(); ++i) {
int u = E[i].fi, v = E[i].se;
int d1 = d[u] + d[v] - 2;
res = add(res, fix(c3(d1) - mul(sqr(d1 - 1), 2)));
d2[u] = add(d2[u], d1 - 1);
d2[v] = add(d2[v], d1 - 1);
}
for(int i = 0;i < n; ++i)
res = add(res, sqr(d2[i]));
return mul(res, P + 1 >> 1);
}
int cnt = 0;
for(int i = 0;i < n; ++i)
B[i].clear();
for(pa p : E) {
B[p.fi].pb(cnt);
B[p.se].pb(cnt++);
}
E.clear();
for(int i = 0;i < n; ++i)
for(int j = 0;j < B[i].size(); ++j)
for(int k = 0;k < j; ++k)
E.pb(mp(B[i][j], B[i][k]));
return Work(cnt, k - 1);
}
}
void Dfs(int x, int k) {
if(x <= k - 1 << 1) {
nw.st[x] = 0; Dfs(x + 1, k);
nw.st[x] = 1; Dfs(x + 1, k);
return ;
}
nw.k = k; nw.clear();
if(nw.Build() != k) return ;
nw.all = true;
int s = nw.Hash(nw.Uni(0, bin[k] - 1, -1)), w;
nw.all = false;
if(~A[s]) return ;
w = Calcw::Work(k, K);
for(int t = 0, u;t < bin[k] - 1; ++t)
if(~(u = nw.Sub(t)))
w = fix(w - A[u]);
A[s] = w;
Ans = add(Ans, mul(w, nw.Calct()));
}
void Pre() {
memset(A, -1, sizeof(A));
bin[0] = 1;
for(int i = 1;i <= 20; ++i) {
bin[i] = bin[i - 1] << 1;
if(i <= 11) Lg[bin[i]] = i;
}
for(int i = 0;i < 2048; ++i)
cnt[i] = cnt[i >> 1] + (i & 1);
for(int i = 0;i <= 5000; ++i) {
C[i][0] = 1;
for(int j = 1;j <= i && j <= 20; ++j)
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
fac[0] = 1;
for(int i = 1;i <= 20; ++i) fac[i] = mul(fac[i - 1], i);
for(int i = 0;i <= 5000; ++i)
for(int j = 0;j <= 20; ++j)
C[i][j] = mul(C[i][j], fac[j]);
}
int main() {
Pre();
n = ri(); K = ri();
for(int i = 1;i < n; ++i)
adds(ri(), ri());
for(int i = 1;i <= K + 1; ++i)
Dfs(1, i);
printf("%d\n", Ans);
return 0;
}
/*
5 5
1 2
2 3
2 5
3 4
*/