LOJ2433. 「ZJOI2018」线图
题目
正解
参考:
官方题解:https://blog.csdn.net/qq_16267919/article/details/79675232
https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p4337
(极度推荐这篇博客,讲解得非常详细)
由于上面的那篇博客讲得比较清楚,所以我这里就简单地概括一下:
首先考虑\(L_k(G)\)中的每个点代表什么:
\(L_1\)中一个点代表\(G\)中一条边。
\(L_2\)中一个点代表\(G\)中相连的两条边。
\(L_3\)中一个点代表\(G\)中相连的三条边(长度为\(3\)的链(包括三元环)、或一个点连出的三条边)。
于是总结出\(L_k(G)\)中的一个点表示\(G\)中的\(k\)条边组成的连通块。
这样表述有些问题,修正一下:\(L_k(G)\)中的一个点表示\(G\)中的不超过\(k\)条边组成的连通块。
并且相同的连通块可能被多个节点表示。
然后又可以发现,对于\(G\)中的一个连通块\(S\),求\(L_k(S)\),它恰好是\(L_k(G)\)的一个连通块。
想要比较好的理解这些性质,建议拿几个样例来手玩一下。上面推荐的那篇博客举的样例不错,手玩一下就能够比较好理解。
由于题目中的\(G\)是一棵树,所以这也可以转化成不超过\(k+1\)个点组成的连通块。
枚举一个不超过\(k+1\)个点的连通块\(T\),计算\(L_k(T)\)中有多少个表示\(T\)的点,记为\(w_T\)。然后在\(G\)中找不同的\(T\)的个数,记为\(t_T\)。最后\(\sum w_Tt_T\)就是答案。
为了方便这里\(T\)指有根树。
计算\(w_T\):
考虑将\(L_k(T)\)的点数算出来,作为\(w_T\)。这时候发现会算多,因为这把\(T\)的联通子图的贡献都算了进去。
于是枚举\(T\)的联通子图\(S\),计算\(\sum w_S\),减去即可。
枚举联通子图的时间相比于下面是比较少的,忽略不计。
如果\(k\leq 4\),可以通过人类智慧将\(L_k(G)\)的点数求出来(此时不需要保证\(G\)是棵树):
\(k=1\)时,答案为边数。
\(k=2\)时,答案为\(G\)中有多少对相邻的边,于是答案为\(\sum C(deg_i,2)\)
\(k=3\)时,答案为\(G\)中有多少条长度为\(3\)的链和一个点连出去三条边的方案数(注意这个每个方案贡献为\(3\))。
\(k=4\)时,考虑\(L_4(G)=L_3(L_1(G))\),通过将\(L_1(G)\)中每个点(对应\(G\)中一条边)的度数算出来,套进\(k=3\)的式子中,化一下式子就出来了。
时间复杂度都是\(O(点数+边数)\)。
具体式子上面推荐的博客有。
\(k\)更大咋办?暴力算出\(L_{k-4}(G)\),然后套用上面的方法算出\(L_4(L_{k-4}(G))\)。
考虑迭代一次点数大概乘\(k\),所以时间复杂度大概为\(O(k^{k-4})\),点数大概开到\(1e5\),边数我开到了\(1e7\)(可能可以少些吧)。
于是这一部分为\(O(1205*k^{k-4})\),其中\(1205\)为大小小于等于\(10\)的本质不同的有根树个数。
有个大优化:做无根树哈希,如果当前的答案之前算过就不用计算。
计算\(t_T\):
设\(f_{i,j}\)表示将有根树\(j\)的根放到\(i\)节点上,多少种方案。
转移的时候枚举有根树\(j\)的根的每个儿子所代表的子树,和\(i\)的儿子匹配。套一个状压DP实现。
这样时间复杂度是\(O(1205*n*2^k)\)
似乎有点慢,加个小优化:不考虑有根树\(j\)的根的每个儿子直接是叶子节点的情况。状压DP之后再将叶子结点用个组合数计算贡献。
注意在算的过程中可能会有重复计算的情况,于是对于有根树\(j\)的根的儿子中,对于每种不同的子树计算相同的个数,答案除以它们的阶乘。(其实也可以在状压的时候不用二进制压表示每个子树选或不选,而是压每种子树用了多少个。这样理论上还快些。)
时间复杂度\(O(1205*n*2^\frac{k}{2})\)。
代码
8k,我醉了……
讲一下实现细节(不一定和程序中一样):
处理不同的有根树,而且还要处理出根的儿子子树的编号。
我程序中的方法从有根树点数小到大枚举,枚举括号序。枚举之后判断儿子子树的编号是否有序,如果不有序就不算。
应该还有一种比较优美的方式:按点数从小到大枚举。枚举直接与根相连的子树的种类,枚举的过程保证编号不上升。然后给枚举出来的子树标号。
无根树哈希大概就是找重心。如果重心有两个就在边中间插一个点。
以其为根求括号序。
由于连出的儿子本是无序的,所以先给连出的儿子的哈希值排序之后再计算。
枚举一棵树的联通子图的时候,先算不包括根的联通子图。设\(sw_T\)表示\(T\)中所有联通子图的\(w\)之和,之前已经处理了,直接算。
然后算包括根的联通子图。先求\(dfs\)序,对于一个点,如果它选,下一个考虑就是它的第一个儿子,否则下一个考虑它子树之外。
最后提醒:模块化!模块化!
(话说那些3k或4k的怎么做到的?)
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <cassert>
#define ll long long
const int N=5010;
const int mo=998244353;
const int inv2=499122177;
int n,k;
//Section:fac,ifac,C(m,n)
ll fac[N],ifac[N];
void initC(int n){
ifac[1]=1;
for (int i=2;i<=n;++i)
ifac[i]=(mo-mo/i)*ifac[mo%i]%mo;
fac[0]=ifac[0]=1;
for (int i=1;i<=n;++i){
fac[i]=fac[i-1]*i%mo;
ifac[i]=ifac[i-1]*ifac[i]%mo;
}
}
ll C(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}
//Section:Graph
struct EDGE{int to;EDGE *las;};
template <int _N,int _M>
struct Graph{
int n=_N;
EDGE e[_M];
int ne;
EDGE *last[_N+1];
void init(int _n=0){n=_n,ne=0,memset(last,0,sizeof(EDGE*)*(n+1));}
void link(int u,int v){e[ne]={v,last[u]};last[u]=e+ne++;}
};
Graph<N,N*2> G;
//Section:Get Rooted Tree
map<int,int> id;
int cnt;
int siz[1300];
vector<int> son[1300];
int lf[1300],same[1300];
//lf:Num of leaves connecting to rt directly
//same:Pro of 1/(Num of same subT connecting to rt directly)!
int rt_hash(int n,int s){return s*22+2*n-1;}
void grt(int x,int k,int sum,int s){
if (sum<0)
return;
if (x==2*k-2){
if (sum)
return;
s=(s<<1)+(1<<2*k-1);
++cnt;
int p=0,_lf=0,_same=1,lst=0,c=0;
for (int i=1,j=1;i<2*k-1;++i){
p+=(s>>i&1?-1:1);
if (p==0){
int a=rt_hash(i-j+1>>1,(s&(1<<i+1)-1)>>j);
if (id.find(a)==id.end()){son[cnt--].clear();return;}
a=id[a];
son[cnt].push_back(a);
_lf+=(a==1);
if (a==lst)
c++;
else{
_same=(ll)_same*ifac[c]%mo;
lst=a,c=1;
}
j=i+1;
}
}
_same=(ll)_same*ifac[c]%mo;
for (int i=1;i<son[cnt].size();++i)
if (son[cnt][i-1]>son[cnt][i]){son[cnt--].clear();return;}
int key=rt_hash(k,s);
id[key]=cnt;
siz[cnt]=k,lf[cnt]=_lf,same[cnt]=_same;
return;
}
grt(x+1,k,sum+1,s);
grt(x+1,k,sum-1,s+(1<<x));
}
//Sectioon: Hash:Unrooted Tree
template <int _N,int _M>
void build_ut(int x,int t,int &n,Graph<_N,_M> &G){//build UT by id of RT
if (siz[t]==1)
return;
for (int i=0;i<son[t].size();++i){
++n,G.link(x,n),G.link(n,x);
build_ut(n,son[t][i],n,G);
}
}
int G0,G1,all;
template <int _N,int _M>
void findG(int x,int fa,Graph<_N,_M> &G,int siz[]){//find center of gravity
siz[x]=1;
bool is=1;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa){
findG(ei->to,x,G,siz);
siz[x]+=siz[ei->to];
is&=(siz[ei->to]<=all>>1);
}
is&=(all-siz[x]<=all>>1);
if (is) (G0?G1:G0)=x;
}
int *_siz,*_key;
bool cmpp(int a,int b){return _siz[a]<_siz[b] || _siz[a]==_siz[b] && _key[a]<_key[b];}
template <int _N,int _M>
void gethash(int x,int fa,Graph<_N,_M> &G,int siz[],int key[]){
siz[x]=1;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa)
gethash(ei->to,x,G,siz,key),siz[x]+=siz[ei->to];
static int p[12];
int cnt=0;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa)
p[cnt++]=ei->to;
_siz=siz,_key=key;
sort(p,p+cnt,cmpp);
key[x]=0;
for (int i=cnt-1,s=0;i>=0;--i){
key[x]+=key[p[i]]<<s;
s+=siz[p[i]]*2;
}
key[x]=(key[x]<<1)+1;
}
template<int _N,int _M>
int ut_hash(Graph<_N,_M> &G,int n,bool rem=1){//rem:whether G can be changed
static int sz[12],key[12];
G0=G1=0,all=n,findG(1,0,G,sz);
int rt=G0;
if (G1){
++n,G.last[n]=NULL;
G.link(n,G0),G.link(n,G1);
for (EDGE *ei=G.last[G0];ei;ei=ei->las)
if (ei->to==G1){ei->to=n;break;}
for (EDGE *ei=G.last[G1];ei;ei=ei->las)
if (ei->to==G0){ei->to=n;break;}
rt=n;
}
gethash(rt,0,G,sz,key);
ll res=rt_hash(key[rt],n)*(G1?-1:1);
if (!rem && G1){
G.ne-=2;
for (EDGE *ei=G.last[G0];ei;ei=ei->las)
if (ei->to==n){ei->to=G1;break;}
for (EDGE *ei=G.last[G1];ei;ei=ei->las)
if (ei->to==n){ei->to=G0;break;}
G.last[n--]=NULL;
}
return res;
}
int rt_to_ut(int t){
static Graph<11,11*2> G;
G.init(siz[t]+1);
int n;
build_ut(1,t,n=1,G);
return ut_hash(G,siz[t]);
}
//Section: Calc w
int w[N],sw[N];
map<int,int> ut_w,ut_sw;
Graph<100010,10000010> L[2];
Graph<11,11*2> T,S;
template <int _N,int _M>
int calc234(Graph<_N,_M> &G,int k){
static int deg[100010];
if (k==0)
return G.n;
if (k==1)
return G.ne/2;
memset(deg,0,sizeof(int)*(G.n+1));
for (int i=1;i<=G.n;++i)
for (EDGE *ei=G.last[i];ei;ei=ei->las)
if (i<ei->to)
deg[i]++,deg[ei->to]++;
ll r=0;
if (k==2){
for (int i=1;i<=G.n;++i)
(r+=(ll)deg[i]*(deg[i]-1)%mo*inv2)%=mo;
}
if (k==3){
for (int i=1;i<=G.n;++i)
for (EDGE *ei=G.last[i];ei;ei=ei->las)
if (i<ei->to)
(r+=(ll)(deg[i]-1)*(deg[ei->to]-1))%=mo;
for (int i=1;i<=G.n;++i)
(r+=(ll)deg[i]*(deg[i]-1)%mo*(deg[i]-2)%mo*inv2)%=mo;
}
if (k==4){
for (int i=1;i<=G.n;++i){
ll d2=0;
for (EDGE *ei=G.last[i];ei;ei=ei->las){
ll d1=deg[i]+deg[ei->to]-2;
d2+=d1-1;
}
(r+=d2*d2)%=mo;
}
r=r*inv2%mo;
for (int i=1;i<=G.n;++i)
for (EDGE *ei=G.last[i];ei;ei=ei->las)
if (i<ei->to){
ll d1=deg[i]+deg[ei->to]-2;
r=((r-(d1-1)*(d1-1))%mo+mo)%mo;
}
for (int i=1;i<=G.n;++i)
for (EDGE *ei=G.last[i];ei;ei=ei->las)
if (i<ei->to){
ll d1=deg[i]+deg[ei->to]-2;
(r+=d1*(d1-1)%mo*(d1-2)%mo*inv2)%=mo;
}
}
return r;
}
template <int _N,int _M>
void trans(Graph<_N,_M> &G,Graph<_N,_M> &F){
F.init(G.ne/2);
for (int i=1;i<=G.n;++i)
for (EDGE *ei=G.last[i];ei;ei=ei->las){
int u=(ei-G.e>>1)+1;
for (EDGE *ej=ei->las;ej;ej=ej->las){
int v=(ej-G.e>>1)+1;
F.link(u,v),F.link(v,u);
}
}
}
int fa[12],in[12],out[12],nowdfn,re[12],num[12];
template<int _N,int _M>
void getdfn(int x,Graph<_N,_M> &G){
in[x]=++nowdfn;
re[nowdfn]=x;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa[x])
fa[ei->to]=x,getdfn(ei->to,G);
out[x]=nowdfn;
}
template<int _N,int _M>
void find_st(int k,int n,Graph<_N,_M> &G,Graph<_N,_M> &S,int &res){
int x=re[k];
if (k>G.n){
if (n<G.n)
(res+=ut_w[ut_hash(S,n,0)])%=mo;
return;
}
find_st(out[x]+1,n,G,S,res);
num[x]=++n;
S.link(num[fa[x]],num[x]);
S.link(num[x],num[fa[x]]);
find_st(k+1,n,G,S,res);
S.ne-=2;
S.last[num[fa[x]]]=S.last[num[fa[x]]]->las;
S.last[num[x]]=S.last[num[x]]->las;
}
int calcw(int t){
int key=rt_to_ut(t);
if (ut_w.find(key)!=ut_w.end()){
sw[t]=ut_sw[key];
return ut_w[key];
}
L[0].init(siz[t]);
int tot;
build_ut(1,t,tot=1,L[0]);
int now=0,las=1;
for (int i=1;i<=k-4;++i){
swap(now,las);
trans(L[las],L[now]);
}
ll res=calc234(L[now],4);
for (int i=0;i<son[t].size();++i)
(sw[t]+=sw[son[t][i]])%=mo;
T.init(siz[t]);
build_ut(1,t,tot=1,T);
nowdfn=0,fa[1]=0,getdfn(1,T);
S.init(siz[t]);
num[1]=1,find_st(2,1,T,S,sw[t]);
res=(res-sw[t]+mo)%mo;
ut_sw[key]=(sw[t]+=res)%=mo;
return ut_w[key]=res;
}
//Section:Calc t
int t[N];
int f[N][1300];
void dp(int x,int fa){
int d=0;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa)
dp(ei->to,x),++d;
static ll g[1024];
for (int j=1;j<=cnt;++j){
if (son[j].size()>d){f[x][j]=0;continue;}
int p=son[j].size()-lf[j];
memset(g,0,sizeof(ll)*(1<<p));
g[0]=1;
for (EDGE *ei=G.last[x];ei;ei=ei->las)
if (ei->to!=fa){
int y=ei->to;
for (int i=(1<<p)-1;i>=0;--i)
for (int k=0;k<p;++k)
if (!(i>>k&1)){
int id=son[j][lf[j]+k];
(g[i|1<<k]+=(ll)g[i]*f[y][id])%=mo;
}
}
f[x][j]=(ll)g[(1<<p)-1]*C(d-p,lf[j])%mo*fac[lf[j]]%mo*same[j]%mo;
(t[j]+=f[x][j])%=mo;
}
}
int main(){
scanf("%d%d",&n,&k);
initC(max(n,k+1));
G.init(n);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
G.link(u,v);
G.link(v,u);
}
if (k<=4){
printf("%d\n",calc234(G,k));
return 0;
}
for (int i=1;i<=k+1;++i)
grt(0,i,0,0);
for (int i=1;i<=cnt;++i)
w[i]=calcw(i);
dp(1,0);
ll ans=0;
for (int i=1;i<=cnt;++i)
(ans+=(ll)w[i]*t[i])%=mo;
printf("%lld\n",ans);
return 0;
}