【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)

题面

BZOJ

洛谷

题解

吉老师的题目是真的神仙啊。

去年去现场这题似乎骗了\(20\)分就滚粗了?

首先\(k=2\)直接算\(k=1\)时的边数就好了。\(k=3\)同理。

这里直接计算每个点的度数就可以做,然后就有\(20\)分了。

我们发现如果企图继续考虑线图应该怎么计算出来,这里是很难做的。

注意到原图是一棵树,所以想想线图和原图之间的关系。

对于做一次线图\(L(G)\)而言,点数显然等于原图的边数。

对于做两次线图\(L^2(G)\)而言,点数就是在\(L(G)\)中的边数。\(L(G)\)中的点在原图中表示边,如果两个点在\(L(G)\)中有边,证明在原图中他们两是有交点的边,即一条长度为\(2\)的路径。

对于做三次线图\(L^3(G)\)而言,即\(L^2(G)\)的边数。而\(L^2(G)\)的边表示他们在\(L(G)\)上是一条长度为\(2\)的边,那么放在原图上,发现可以是一个长度为\(3\)的链,亦或者是三条共点的边。

那么如果继续口胡呢?\(L^4(G)\)是啥呢?那么就是\(L^2(L^2(G))\)。考虑在\(L^2(G)\)上找长度为\(2\)的路径。显然长度为\(4\)的链是可以的,然后四条共点的边也是可以的。其实回到\(L^2(2)\)的边的含义,那么不难发现\(L^4(G)\)也就是原图中两条长度为\(2\)的路径拼起来,如果要拼起来显然要有一个交点,所以实际上就是一棵拥有\(4\)条连在一起的东西,放在一棵树的原图中,就是一个点数为\(5\)的树。

那么这么分析一下,似乎发现前面的\(L(G)\)对应着边数为\(1\)、点数为\(2\)的树。\(L^2(G)\)对应着边数为\(2\),点数为\(3\)的树。\(L^3(G)\)对应着边数为\(3\)、点数为\(4\)的树......

真的?就这么简单?

然而仔细想想这样子似乎有点锅。因为我们发现在线图的构建过程中是会有环的出现,最简单的例子就是如果\(G\)并不是题目给出的树的话,那么显然原图中的三元环也要计入答案。

所以来改正一下我们的措辞,\(L^k(G)\)的每一个点对应着原图\(G\)中的一个边数不超过\(k\)的联通的导出子图的个数。注意这里的用词,是一个点对应着一个导出子图,而不是每一个导出子图对应着一个点,同时也意味着一个导出子图可能被多个点所对应。

而原图就是一棵树,所以实际上的联通导出子图只可能是一棵树的形态。

对于每个边数不超过\(k\)的子树的贡献太慢了,显然是把每种子树都统计一下个数,然后再对于每种子树计算贡献然后统计答案。

贡献不好算?直接暴力算\(k\)次不就完事了?然而这样子就会发现复杂度单次计算的复杂度很爆炸。

我们发现\(k\)很小的时候可以直接推式子来快速计算,所以我们只需要模拟出一个较小的\(k\)的图然后直接计算。

那么我们来推推式子?假设\(n\)是点数,\(m\)是边数。

对于\(L(G)\),显然\(ans=m\)。

对于\(L^2(G)\),\(ans=\sum_{i=1}^n {deg(i)\choose 2}\)

对于\(L^3(G)\),考虑长度为三的链以及三元环的贡献\(\sum _{(u,v)\in E}(deg(u)-1)*(deg(v)-1)\),即枚举中间那条边,考虑以这条边的两个端点再延伸出去。然后再考虑一个点挂三条边的贡献,就是\(3\sum_{i=1}^n {deg(i)\choose 3}\),乘三的原因是这样的三条边在求线图后成环,贡献了三个点。两个式子求和就是\(L^3(G)\)的贡献了。

然后\(L^4(G)\)怎么算?直接算肯定不方便,所以变形一下成了\(L^3(L(G))\)。而\(L(G)\)每个点的度数是很好算的,为\(deg((u,v))=deg(u)+deg(v)-2,(u,v)\in E\)。

而计算\(L^3(G)\)时对于每条边计算贡献时,我们不可能把每条边全部整出来算。这样子考虑,\(L(G)\)中的一个点表示的是\(G\)中的一条边,而\(L(G)\)中的一条边,表示的是原图中的一个长度为\(2\)的链,现在要顺次考虑每一条边的贡献,等价于在原图中考虑每一个长度为\(2\)的链的贡献。而一个长度为\(2\)的链由三个点构成,所以我们枚举其中中间的那个点,那么这两条边都从这个点射出。

所以令\(S(u)\)表示以\(u\)为端点的边\((u,v)\in E\)的所有边的\(deg((u,v))-1\)的和,那么贡献就是\(\frac{1}{2}S(u)^2\),然而这个东西算重了,每条边在每个端点时都被自己和自己匹配了\(\frac{1}{2}\)次,也就是一共被多算了\(1\)次,所以还需要减去\(\sum_{(u,v)\in E}(deg((u,v))-1)^2\)。

所以\(L^4(G)\)的答案就是\(\frac{1}{2}\sum_{i=1}^n S(i)^2-\sum_{i=1}^m (deg(i)-1)^2+3\sum_{i=1}^m{deg(i)\choose 3}\)。

再往后似乎也能继续推,但是也没有什么必要了。

好的,那么现在我们要做的就是两个事情,统计每种边数不超过\(k\)的树的贡献,以及它在原树中的出现次数。

我们先考虑怎么求解边数不超过\(k\)的树的贡献,可以类似括号序列的方法爆搜树,左括号表示当前点加入一个儿子,右括号表示回到父亲,但是这样会计算出同构的树,所以再树哈希一下去重。

那么我们计算一棵树\(T\)的\(L^k(T)\)的点数,就是前文所说的,只需要模拟出\(L^{k-4}(T)\),然后直接计算即可。

接着是考虑如何计算贡献,显然就是对于当前联通块而言减去其所有联通导出子图的贡献,这个容斥处理即可。

然后就是对于每棵搜出来的树,如何计算它在原树上的出现次数。

我们来做一个\(dp\),设\(f[i][r]\)表示以原树上的\(i\)节点为根节点,当前点可以匹配上目标树上的\(r\)的子树的方案数。

转移的时候枚举当前点匹配目标树上的哪个节点,那么它的儿子就要和目标树上枚举的这个节点对应,这个直接\(dp\)就好了。注意一个小问题,因为子树是可以同构的,所以这里要考虑可重排列的贡献。

当然,如果直接暴力\(dp\)复杂度是比较难接受的,这里做一个小小的优化,把目标树上的叶子节点全部删掉,在匹配完之后再考虑当前点挂的叶子节点,这样子可以直接用一个组合数计算方案数,同时可以把这里要求解的点数优化很多。

本机\(2.2s\),洛谷和\(LOJ\)都能过,\(UOJ\)我卡不动了,\(BZOJ\)今天挂了,所以就这样了。

upd:BZOJ上莫名CE。。。。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<algorithm>
  7. #include<set>
  8. #include<map>
  9. using namespace std;
  10. #define MOD 998244353
  11. #define inv2 499122177
  12. #define ull unsigned int
  13. #define MAX 5050
  14. void halt(){exit(0);}
  15. inline int read()
  16. {
  17. int x=0;bool t=false;char ch=getchar();
  18. while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
  19. if(ch=='-')t=true,ch=getchar();
  20. while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
  21. return t?-x:x;
  22. }
  23. int fpow(int a,int b)
  24. {
  25. int s=1;
  26. while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
  27. return s;
  28. }
  29. int n,k,ans;
  30. int S[MAX*2000],dg[MAX*2000];
  31. int C[MAX][15],JC[15];
  32. const ull base1=998,base2=244,base3=353;
  33. struct Graph
  34. {
  35. struct Line{int v,next;}e[MAX*2000];
  36. int h[MAX*2000],cnt,n;
  37. void pre(int N){n=N;cnt=2;for(int i=1;i<=n;++i)h[i]=0;}
  38. inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
  39. int Calc4()
  40. {
  41. int ans=0;for(int i=1;i<=n;++i)dg[i]=S[i]=0;
  42. for(int u=1;u<=n;++u)
  43. for(int i=h[u];i;i=e[i].next)dg[e[i].v]+=1;
  44. for(int u=1;u<=n;++u)
  45. for(int i=h[u];i;i=e[i].next)
  46. S[u]+=dg[u]+dg[e[i].v]-3;
  47. for(int i=1;i<=n;++i)ans=(ans+1ll*S[i]*S[i])%MOD;
  48. ans=1ll*ans*inv2%MOD;
  49. for(int i=2;i<cnt;i+=2)
  50. {
  51. int d=dg[e[i].v]+dg[e[i^1].v]-2;
  52. ans=(ans+MOD-(d-1)*(d-1))%MOD;
  53. ans=(ans+1ll*d*(d-1)*(d-2)/2)%MOD;
  54. }
  55. return ans;
  56. }
  57. }T,E[2];
  58. int lg[MAX],bul[MAX];
  59. int lb(int x){return x&(-x);}
  60. struct Tree
  61. {
  62. ull f[15],Q[15];int n,E[15],size[15],jc[15];
  63. void Add(int u,int v){E[u]|=1<<v;E[v]|=1<<u;}
  64. void Resize(int u,int ff)
  65. {
  66. size[u]=1;
  67. for(int i=E[u];i;i-=lb(i))
  68. if(lg[lb(i)]!=ff)
  69. Resize(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
  70. }
  71. void dfs(int u,int ff)
  72. {
  73. size[u]=1;
  74. for(int i=E[u];i;i-=lb(i))
  75. if(lg[lb(i)]!=ff)
  76. dfs(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
  77. int son=0;for(int i=E[u];i;i-=lb(i))Q[++son]=f[lg[lb(i)]];
  78. sort(&Q[1],&Q[son+1]);f[u]=size[u];jc[u]=1;Q[son+1]=Q[son]+1;
  79. for(int i=1;i<=son;++i)f[u]=f[u]*base1+Q[i];Q[0]=Q[1]+1;
  80. for(int i=1,s=1,cnt=1;i<=son+1;++i)
  81. if(Q[i]==Q[i-1])++cnt,s=1ll*s*cnt%MOD;
  82. else jc[u]=1ll*jc[u]*s%MOD,cnt=s=1;
  83. jc[u]=fpow(jc[u],MOD-2);
  84. f[u]*=size[u]*base2+base3;
  85. }
  86. int sz[15];ull F[15];
  87. void CalcHash(int u,int ff,int S,int &T)
  88. {
  89. sz[u]=1;T|=1<<u;
  90. for(int i=E[u]&S;i;i-=lb(i))
  91. if(lg[lb(i)]!=ff)
  92. CalcHash(lg[lb(i)],u,S,T),sz[u]+=sz[lg[lb(i)]];
  93. int son=0;for(int i=E[u]&S;i;i-=lb(i))Q[++son]=F[lg[lb(i)]];
  94. sort(&Q[1],&Q[son+1]);F[u]=sz[u];
  95. for(int i=1;i<=son;++i)F[u]=F[u]*base1+Q[i];
  96. F[u]*=sz[u]*base2+base3;
  97. }
  98. void Hash(){dfs(0,0);}
  99. int check(int S)
  100. {
  101. int mx=lg[lb(S)];
  102. for(int i=0;i<n;++i)if(S&(1<<i))if(size[mx]<size[i])mx=i;
  103. int vis=0;CalcHash(mx,0,S,vis);return vis==S?mx:-1;
  104. }
  105. void clear(){for(int i=0;i<15;++i)E[i]=size[i]=sz[i]=F[i]=f[i]=jc[i]=Q[i]=0;}
  106. };
  107. void SpecialCheck()
  108. {
  109. int ans=0;
  110. if(k==1)printf("%d\n",n-1),halt();
  111. if(k==2)
  112. {
  113. for(int i=1;i<=n;++i)ans=(ans+1ll*dg[i]*(dg[i]-1)/2)%MOD;
  114. printf("%d\n",ans);halt();
  115. }
  116. if(k==3)
  117. {
  118. for(int i=2;i<T.cnt;i+=2)
  119. {
  120. int d=dg[T.e[i].v]+dg[T.e[i^1].v]-2;
  121. ans=(ans+1ll*d*(d-1)/2)%MOD;
  122. }
  123. printf("%d\n",ans);halt();
  124. }
  125. if(k==4)printf("%d\n",T.Calc4()),halt();
  126. }
  127. void GetLineGraph(Graph &a,Graph &b)
  128. {
  129. int N=0;
  130. for(int u=1;u<=a.n;++u)
  131. for(int i=a.h[u];i;i=a.e[i].next)N+=1;
  132. N/=2;b.pre(N);
  133. for(int u=1;u<=a.n;++u)
  134. for(int i=a.h[u];i;i=a.e[i].next)
  135. for(int j=a.e[i].next;j;j=a.e[j].next)
  136. b.Add(i>>1,j>>1),b.Add(j>>1,i>>1);
  137. }
  138. map<ull,int> M;
  139. int CalcNode(int k)
  140. {
  141. int nw=0,pw=1;k-=4;
  142. while(k--)GetLineGraph(E[nw],E[pw]),nw^=1,pw^=1;
  143. return E[nw].Calc4();
  144. }
  145. int CalcValue(Tree &a,int k)
  146. {
  147. a.Hash();E[0].pre(a.n);
  148. for(int i=0;i<a.n;++i)
  149. for(int j=a.E[i];j;j-=lb(j))
  150. E[0].Add(i+1,lg[lb(j)]+1),E[0].Add(lg[lb(j)]+1,i+1);
  151. int ret=CalcNode(k);
  152. for(int i=1;i<(1<<a.n)-1;++i){int p=a.check(i);if(~p)if(M.find(a.F[p])!=M.end())ret=(ret+MOD-M[a.F[p]])%MOD;}
  153. return M[a.f[0]]=ret;
  154. }
  155. int leaf[15],f[MAX][15];
  156. bool Leaf[15];
  157. Tree now;int pre[MAX];
  158. void dfs(int u,int ff)
  159. {
  160. int son=0;
  161. for(int i=T.h[u];i;i=T.e[i].next)
  162. if(T.e[i].v!=ff)++son,dfs(T.e[i].v,u);
  163. for(int i=0;i<now.n;++i)
  164. {
  165. if(Leaf[i])continue;
  166. if(son<bul[now.E[i]]){f[u][i]=0;continue;}
  167. for(int j=now.E[i];j;j=(j-1)&now.E[i])pre[j]=0;
  168. pre[0]=1;
  169. for(int j=T.h[u];j;j=T.e[j].next)
  170. {
  171. if(T.e[j].v==ff)continue;
  172. for(int k=now.E[i];k;k=(k-1)&now.E[i])
  173. for(int p=k;p;p-=lb(p))
  174. pre[k]=(pre[k]+1ll*pre[k^lb(p)]*f[T.e[j].v][lg[lb(p)]])%MOD;
  175. }
  176. f[u][i]=1ll*pre[now.E[i]]*now.jc[i]%MOD*C[son-bul[now.E[i]]][leaf[i]]%MOD*JC[leaf[i]]%MOD;
  177. }
  178. }
  179. int CalcTimes(Tree &a)
  180. {
  181. now=a;
  182. for(int i=0;i<a.n;++i)leaf[i]=0,Leaf[i]=false;
  183. for(int i=0;i<a.n;++i)
  184. for(int j=a.E[i];j;j-=lb(j))
  185. if(a.size[lg[lb(j)]]==1)
  186. ++leaf[i],now.E[i]^=lb(j);
  187. for(int i=0;i<a.n;++i)if(a.size[i]==1)Leaf[i]=true;
  188. now.Resize(0,0);dfs(1,0);int ret=0;
  189. for(int i=1;i<=n;++i)ret=(ret+f[i][0])%MOD;
  190. return ret;
  191. }
  192. int St[MAX<<1],fa[MAX];
  193. set<ull> Vis;
  194. Tree p;
  195. void dfsTrees(int x,int c,int sp,int lim)
  196. {
  197. if(x==(lim-1)*2+1)
  198. {
  199. int now=0,tot=0;p.clear();
  200. for(int i=1;i<x;++i)
  201. if(St[i]==1)p.E[now]|=1<<(++tot),fa[tot]=now,now=tot;
  202. else now=fa[now];
  203. p.n=lim;p.Hash();
  204. if(Vis.find(p.f[0])!=Vis.end())return;Vis.insert(p.f[0]);
  205. ans=(ans+1ll*CalcValue(p,k)*CalcTimes(p))%MOD;
  206. return;
  207. }
  208. if(c)St[x]=-1,dfsTrees(x+1,c-1,sp,lim);
  209. if(sp<lim-1)St[x]=1,dfsTrees(x+1,c+1,sp+1,lim);
  210. }
  211. int main()
  212. {
  213. n=read();k=read();T.pre(n);
  214. for(int i=1;i<n;++i)
  215. {
  216. int u=read(),v=read();dg[u]+=1,dg[v]+=1;
  217. T.Add(u,v),T.Add(v,u);
  218. }
  219. SpecialCheck();
  220. for(int i=2;i<MAX;++i)lg[i]=lg[i>>1]+1;
  221. for(int i=1;i<MAX;++i)bul[i]=bul[i^lb(i)]+1;
  222. for(int i=0;i<MAX;++i)C[i][0]=1;JC[0]=1;
  223. for(int i=1;i<15;++i)JC[i]=1ll*JC[i-1]*i%MOD;
  224. for(int i=1;i<MAX;++i)
  225. for(int j=1;j<=i&&j<15;++j)
  226. C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
  227. for(int i=1;i<=k+1;++i)dfsTrees(1,0,0,i),Vis.clear();
  228. printf("%d\n",ans);
  229. return 0;
  230. }

【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)的更多相关文章

  1. UOJ#373. 【ZJOI2018】线图 搜索,树哈希,动态规划

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ373.html 前言 真是一道毒瘤题.UOJ卡常毒瘤++.我卡了1.5h的常数才过QAQ Orzjry 标算居然是指数做法 ...

  2. 洛谷P4337 [ZJOI2018]线图(状压+搜索+乱搞)

    题面 传送门 题解 妈呀调了我整整一天-- 题解太长了不写了可以去看\(shadowice\)巨巨的 //minamoto #include<bits/stdc++.h> #define ...

  3. 【ZJOI 2018】线图(树的枚举,hash,dp)

    线图 题目描述 九条可怜是一个热爱出题的女孩子. 今天可怜想要出一道和图论相关的题.在一张无向图 $G$ 上,我们可以对它进行一些非常有趣的变换,比如说对偶,又或者说取补.这样的操作往往可以赋予一些传 ...

  4. 【BZOJ3162】独钓寒江雪(树哈希,动态规划)

    [BZOJ3162]独钓寒江雪(树哈希,动态规划) 题面 BZOJ 题解 忽然翻到这道题目,突然发现就是前几天一道考试题目... 题解: 树哈希,既然只考虑这一棵树,那么,如果两个点为根是同构的, 他 ...

  5. Colored Sticks (字典树哈希+并查集+欧拉路)

    Time Limit: 5000MS   Memory Limit: 128000K Total Submissions: 27704   Accepted: 7336 Description You ...

  6. pandas 生成并排放置的条形图和箱线图

    1.代码 import numpy as np import pandas as pd import matplotlib.pyplot as plt # 生成数据,创建 DataFrame np.r ...

  7. Tableau绘制K线图、布林线、圆环图、雷达图

    Tableau绘制K线图.布林线.圆环图.雷达图 本文首发于博客冰山一树Sankey,去博客浏览效果更好.直接右上角搜索该标题即可 一. K线图 1.1 导入数据源 1.2 拖拽字段 将[日期]托到列 ...

  8. 一起来玩echarts系列(一)------箱线图的分析与绘制

    一.箱线图 Box-plot 箱线图一般被用作显示数据分散情况.具体是计算一组数据的中位数.25%分位数.75%分位数.上边界.下边界,来将数据从大到小排列,直观展示数据整体的分布情况. 大部分正常数 ...

  9. K线图学习

    本博文(适合入门的股民朋友)内容来自网络,股市有风险,入市需谨慎 一.起源 K线图(Candlestick Charts)又称蜡烛图.日本线.阴阳线.棒线等,常用说法是“K线”,起源于日本十八世纪德川 ...

随机推荐

  1. jmeter分布式压测(多台电脑一起压测)

    (1)在Windows下运行 操作步骤: 1)     有多台电脑,每台电脑上都有jmeter,而且这几台电脑都互相能ping通. 2)     在我的电脑的jmeter的配置文件bin目录下的jme ...

  2. 查看mysql数据库连接数、并发数相关信息

    查看mysql数据库连接数.并发数相关信息. - caodongfang126的博客 - CSDN博客 https://blog.csdn.net/caodongfang126/article/det ...

  3. [服务器]Gartner:2018年第四季度全球服务器收入增长17.8% 出货量增长8.5%

    Gartner:2018年第四季度全球服务器收入增长17.8% 出货量增长8.5% Gartner 是不是也是花钱买榜的主啊.. 简单看了一下 浪潮2018Q4的营收18亿刀 (季度营收110亿人民币 ...

  4. js对input框的可编辑属性设置

    添加disabled属性 $('#areaSelect').attr("disabled",true); $('#areaSelect').attr("disabled& ...

  5. 安装sqlprompt

    特别说明:注册机会报毒,安装前请先关闭杀毒软件!下载好附件之后解压,打开SQLPrompt_7.2.0.241.exe按照提示安装完成.安装完成后断网!打开数据库,会在菜单栏中看到SQL Prompt ...

  6. mapreduce join

    MapReduce Join 对两份数据data1和data2进行关键词连接是一个很通用的问题,如果数据量比较小,可以在内存中完成连接. 如果数据量比较大,在内存进行连接操会发生OOM.mapredu ...

  7. js外部调用layui.use中的函数的写法

    layui模块化的写法固然不错,但也有让人不适应的一些地方 外部调用函数的写法就让人不太舒服 需要在函数名前面加上window这个前缀,就不太舒服 补充:window前缀,是全局变量的声明方式 如下: ...

  8. MySQL的FIND_IN_SET()函数

    今天在做项目时,看到了一个从没见过的MySQL函数——FIND_IN_SET(),顿时就产生了浓郁的兴趣,然后就搜了搜,翻了翻. 语法:FIND_IN_SET(str,strlist) 定义: 1. ...

  9. 待解决ava.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Method)

    java.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Method) at ja ...

  10. Javascript 实现复制(Copy)动作方法大全

    一.实现点击按钮,复制文本框中的的内容 <script type="text/javascript"> function copyUrl2() { var Url2=d ...