UOJ#373. 【ZJOI2018】线图 搜索,树哈希,动态规划
原文链接www.cnblogs.com/zhouzhendong/p/UOJ373.html
前言
真是一道毒瘤题。UOJ卡常毒瘤++。我卡了1.5h的常数才过QAQ
Orzjry
标算居然是指数做法。
题解
1. 感受一下线图上点的含义
1.1 一阶线图
L(G) 上的一个点对应 G 中的一条边。
1.2 二阶线图
$L^2(G)$ 上一个点对应 G 中的一条包含 3 个节点的路径。
1.3 三阶线图
$L^3(G)$ 上一个点对应 G 中的一朵4个节点的菊花或者一条包含 4 个节点的路径,在特殊情况下,这条路径首尾相连,形成三元环。
1.4 k 阶线图
$L^k(G)$ 上一个点对应 G 中的一个节点数不超过 k+1 的连通块。
1.5 性质
在原图中同构的两个联通块在 $L^k(G)$ 中对应的节点数相同。
那么如果对于每一种不超过 k+1 个节点的连通块 g,我们都能求出它在 $L^k(G)$ 中对应的节点个数,而且我们能统计 G 中与 g 同构的连通块个数,那么我们就可以解决这个问题。
2. 找到所有不超过 k+1 个点的联通块 g
我们发现 G 是一棵树,所以 g 也是一棵树。
为了方便,我们先搜出所有不同构的不多于 10 个节点的有根树。
搜的方法很多吧,树哈希、括号序列等等,不展开介绍。
搜出来了!居然只有 1205 个。
3. 手推 1~4 阶线图节点数公式
设 e 和 V 分别为 G 的边集和点集。
3.1 一阶线图
n - 1
3.2 二阶线图
设 $d[x]$ 为节点 x 的度数,则答案为
$$\sum_{x\in V} \binom {d[x]} 2 $$
3.3 三阶线图
容易发现答案为
$$\sum_{(x,y)\in e} (d[x]-1)(d[y]-1) + \sum_{x\in V} 3\binom {d[x]} 3$$
3.4 四阶线图
这个东西非常不好算啊。
我们把它转化成算 $L^3(L(G))$ 的节点个数。
设 $d[x,y]$ 表示 $G$ 中的边 $(x,y)$ 在 $L(G)$ 中对应的点的度数。则
$$d[x,y] = d[x]+d[y]-2$$
则答案为
$$\sum_{(x,y),(y,z)\in e} (d[x,y]-1)(d[y,z]-1) + \sum_{(x,y)\in e} 3\binom {d[x,y]} 3$$
考虑如何优化这个式子:
设
$$S[x] = \sum_{(x,y) \in G} d[x,y] $$
预处理处 S[x] ,剩下的式子就由读者自行推导。
4.对于所有不超过 k+1 个节点的图 g,都算出它在 k 次线图中的对应点个数(设为 v1[g])
注意,在 k 次线图中的对应点个数,不是 g 的 k 次线图的点数。
但是我们可以考虑先求出 g 的 k 次线图的点数,然后再减掉 g 的所有子图的贡献。由于 g 的子图的贡献也是一个和原问题模型类似的问题,所以不加展开介绍。
那么如何求 g 的 k 次线图的点数?
我们先暴力展开 k-4 层,最后 4 层用之前提到的式子来计算。
时间复杂度 O(玄) 。
5. 求一个树上有多少个子图与另一个树同构
这是为了求出 G 中有多少个图 g ,只要求出了这个东西,那么乘上 g 在 k 次线图中的对应点数,就可以得到 g 对答案的贡献。
考虑 dp 。
设 dp[i][j] 表示以 G 的节点 i 为根,放上有根树 j 的方案数。
考虑如何直接合并子树的贡献。
枚举一下当前节点 i 的 j,把 j 的相同子树缩到一起,搞一个状压 dp。由于子树 size 之和很小以及节点个数少的子树种类很少,所以状压出来的状态很少,可以当做一个常数。于是只要枚举一下 i 的子树,然后用对应的状态进行转移即可。
时间复杂度 $O(nS\cdot C)$ 。其中 S=1205 表示子图种类数,C 表示刚才提到的那个常数。
6. 其他
到此为止这题的做法以及讲完了。
但是实际做这题的时候还可能会被卡常数,在使用各种卡常技巧之外,这里提供另一种卡常思路:
算 v1[g] 的时候,我们只算所有不同构的无根树的 v1[g] ,因为不同构的无根树的种类很少,比不同构的有根树要少一个数量级,大约在 400 种以下。
这里需要用到判定无根树是否同构。
我们只要转化成有根树就好了:以直径的中点/重心为根。(直径的中点/重心可能在一条边上)
代码
#pragma GCC optimize("Ofast","inline") #include <bits/stdc++.h> #define clr(x) memset(x,0,sizeof (x)) #define For(i,a,b) for (int i=a;i<=b;i++) #define Fod(i,b,a) for (int i=b;i>=a;i--) #define pb(x) push_back(x) #define mp(x,y) make_pair(x,y) #define fi first #define se second #define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I') #define outval(x) printf(#x" = %d\n",x) #define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("") #define outtag(x) puts("----------"#x"----------") #define outarr(a,L,R) printf(#a"[%d...%d] = ",L,R);\ For(_v2,L,R)printf("%d ",a[_v2]);puts(""); using namespace std; typedef long long LL; typedef vector <int> vi; LL read(){ LL x=0,f=0; char ch=getchar(); while (!isdigit(ch)) f|=ch=='-',ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return f?-x:x; } const int N=5010,mod=998244353; void Add(int &x,int y){ if ((x+=y)>=mod) x-=mod; } void Del(int &x,int y){ if ((x-=y)<0) x+=mod; } int Pow(int x,int y){ int ans=1; for (;y;y>>=1,x=(LL)x*x%mod) if (y&1) ans=(LL)ans*x%mod; return ans; } int inv2=Pow(2,mod-2); int inv3=Pow(3,mod-2); int inv6=Pow(6,mod-2); int inv24=Pow(24,mod-2); int n,k; vi e[N]; namespace so1{ int in[100005]; int preval[N]; void prework(){ For(i,0,N-1) preval[i]=(LL)i*(i-1)*(i-2)/2%mod; } int Get_ans1(int n,vi *e){ int ans=0; For(i,1,n) Add(ans,in[i]); return (LL)ans*inv2%mod; } int Get_ans2(int n,vi *e){ int ans=0; For(i,1,n) Add(ans,(LL)in[i]*(in[i]-1)/2%mod); return ans; } int Get_ans3(int n,vi *e){ int ans=0; For(x,1,n) for (auto y : e[x]) Add(ans,(LL)(in[x]-1)*(in[y]-1)%mod); ans=(LL)ans*inv2%mod; For(x,1,n) Add(ans,(LL)in[x]*(in[x]-1)%mod*(in[x]-2)/2%mod); return ans; } int Get_ans4(int n,vi *e){ static int s[100005]; For(x,1,n){ s[x]=0; for (auto y : e[x]) Add(s[x],in[x]+in[y]-3); } int ans=0; For(x,1,n) for (auto y : e[x]) if (x<y){ int tmp=in[x]+in[y]-2,tx=s[x],ty=s[y]; Add(ans,preval[tmp]); Del(tx,tmp-1),Del(ty,tmp-1); tmp=(tmp&1)?(tmp-1)>>1:(tmp+mod-1)>>1; Add(ans,(LL)tmp*(tx+ty)%mod); } return ans; } int solve(int n,vi *e,int k){ For(i,1,n) in[i]=e[i].size(); if (k==1) return Get_ans1(n,e); else if (k==2) return Get_ans2(n,e); else if (k==3) return Get_ans3(n,e); else if (k==4) return Get_ans4(n,e); return -1; } } namespace Unique_tree{ const int S=1230; vi son[S],tmp; int size[S],cnt=0,Size; map <vi,int> Map; void search(int k){ if (k==0){ Map[tmp]=++cnt; size[cnt]=Size; son[cnt]=tmp; return; } search(k-1); if (Size+size[k]<=10){ Size+=size[k]; tmp.pb(k); search(k); tmp.pop_back(); Size-=size[k]; } } void Get_Tree(){ Map.clear(); Map[son[++cnt]]=1; size[1]=1; For(i,1,cnt){ if (size[i]==10) continue; Size=size[i]+1; tmp.clear(); tmp.pb(i); search(i); } } vi e[N]; int Add_Edge(int x,int cnt){ int id=cnt; cnt++; for (auto y : son[x]){ e[id].pb(cnt),e[cnt].pb(id); cnt=Add_Edge(y,cnt); } return cnt; } int dis[N],fa[N]; int far; void find_far(int x,int pre,int d){ fa[x]=pre; if (!far||d>dis[far]) far=x; dis[x]=d; for (auto y : e[x]) if (y!=pre) find_far(y,x,d+1); } int dfs_get(int x,int pre){ vector <int> v; for (auto y : e[x]) if (y!=pre) v.pb(dfs_get(y,x)); sort(v.begin(),v.end()); reverse(v.begin(),v.end()); return Map[v]; } int Get_Hash(int x){ For(i,1,size[x]) e[i].clear(); Add_Edge(x,1); int a,b; far=0,find_far(1,0,0),a=far; far=0,find_far(a,0,0),b=far; int mid=b; Fod(i,dis[b]/2,1) mid=fa[mid]; if (dis[b]&1){ int va=dfs_get(mid,fa[mid]); int vb=dfs_get(fa[mid],mid); if (va>vb) swap(va,vb); return (va<<14)|vb; } else return dfs_get(mid,0); } } using Unique_tree::S; using Unique_tree::son; using Unique_tree::size; using Unique_tree::cnt; //int mxtmp=0; namespace line_graph_calc{ const int mx=1e5+5; int e[mx][100],ec[mx]; int e2[mx][100],ec2[mx]; int ed[2][10000005],ed2[2][10000005],cnt1,cnt2; int n; void Trans(){ int _n=cnt1; cnt2=0; For(i,1,_n) ec2[i]=0; For(i,1,n) for (int j=1;j<=ec[i];j++) for (int k=j+1;k<=ec[i];k++){ int x=e[i][j],y=e[i][k]; ed2[0][++cnt2]=x; ed2[1][cnt2]=y; e2[x][++ec2[x]]=cnt2; e2[y][++ec2[y]]=cnt2; } For(i,1,cnt2) ed[0][i]=ed2[0][i],ed[1][i]=ed2[1][i]; cnt1=cnt2; n=_n; For(i,1,n){ ec[i]=ec2[i]; For(j,1,ec[i]) e[i][j]=e2[i][j]; } } vi ee[mx]; int calc(int _n,vi *E,int k){ static int g[15][15]; n=_n; clr(g); For(i,1,n) for (auto j : E[i]) g[i][j]=1; For(i,1,n) ec[i]=0; cnt1=0; For(i,1,n) For(j,i+1,n) if (g[i][j]){ ed[0][++cnt1]=i; ed[1][cnt1]=j; e[i][++ec[i]]=cnt1; e[j][++ec[j]]=cnt1; } while (k>4) Trans(),k--; For(i,1,n){ ee[i].clear(); For(x,1,ec[i]){ int j=e[i][x]; ee[i].pb(ed[0][j]==i?ed[1][j]:ed[0][j]); } } return so1::solve(n,ee,k); } } int v1[S],v2[S]; int Add_Edge(int x,vi *e,int cnt){ int id=cnt; cnt++; for (auto y : son[x]){ e[id].pb(cnt),e[cnt].pb(id); cnt=Add_Edge(y,e,cnt); } return cnt; } void Get_v1(int x){ static vi e[20]; For(i,0,19) e[i].clear(); int cnt=Add_Edge(x,e,1); assert(cnt==size[x]+1); v1[x]=line_graph_calc::calc(size[x],e,k); } int dp[N][S]; void Update(vi *e,int x,int pre,int i,vector <pair <int,int> > &v){ static int f[S],wei[S],nxtwei[S]; int s=1; for (auto j : v) wei[j.fi]=s,s*=j.se+1,nxtwei[j.fi]=s; f[0]=1; For(i,1,s-1) f[i]=0; for (auto y : e[x]){ if (y==pre) continue; Fod(j,s-1,0) if (f[j]) for (auto k : v) if (dp[y][k.fi]&&j%nxtwei[k.fi]+wei[k.fi]<nxtwei[k.fi]) Add(f[j+wei[k.fi]],(LL)f[j]*dp[y][k.fi]%mod); } dp[x][i]=f[s-1]; } vector <pair <int,int> > vec; void solve(vi *e,int x,int pre){ for (auto y : e[x]) if (y!=pre) solve(e,y,x); For(i,1,cnt){ vec.clear(); for (auto j : son[i]) if (vec.empty()||j!=vec.back().fi) vec.pb(mp(j,1)); else vec.back().se++; Update(e,x,pre,i,vec); } } void dfs_del(int x,int i){ For(j,1,cnt) if (size[j]<size[i]) Del(v1[i],(LL)v1[j]*dp[x][j]%mod); for (auto y : son[x]) dfs_del(y,i); } int Hash[S]; int main(){ so1::prework(); n=read(),k=read(); For(i,1,n-1){ int x=read(),y=read(); e[x].pb(y),e[y].pb(x); } if (k<=4) return printf("%d\n",so1::solve(n,e,k)),0; Unique_tree::Get_Tree(); For(i,1,cnt) Hash[i]=Unique_tree::Get_Hash(i); int equal_cnt=0; For(i,1,cnt){ int flag=0; Fod(j,i-1,1) if (Hash[i]==Hash[j]){ if (size[i]>8) equal_cnt++; flag=1,v1[i]=v1[j]; break; } if (!flag) Get_v1(i); } For(i,1,cnt) solve(son,i,0); For(i,1,cnt) dfs_del(i,i); solve(e,1,0); For(i,1,cnt){ v2[i]=0; For(j,1,n) Add(v2[i],dp[j][i]); } int ans=0; For(i,1,cnt) Add(ans,(LL)v1[i]*v2[i]%mod); cout<<ans<<endl; return 0; }