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;
}
UOJ#373. 【ZJOI2018】线图 搜索,树哈希,动态规划的更多相关文章
- 【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)
[BZOJ5211][ZJOI2018]线图(树哈希,动态规划) 题面 BZOJ 洛谷 题解 吉老师的题目是真的神仙啊. 去年去现场这题似乎骗了\(20\)分就滚粗了? 首先\(k=2\)直接算\(k ...
- 【ZJOI 2018】线图(树的枚举,hash,dp)
线图 题目描述 九条可怜是一个热爱出题的女孩子. 今天可怜想要出一道和图论相关的题.在一张无向图 $G$ 上,我们可以对它进行一些非常有趣的变换,比如说对偶,又或者说取补.这样的操作往往可以赋予一些传 ...
- 洛谷P4337 [ZJOI2018]线图(状压+搜索+乱搞)
题面 传送门 题解 妈呀调了我整整一天-- 题解太长了不写了可以去看\(shadowice\)巨巨的 //minamoto #include<bits/stdc++.h> #define ...
- UOJ#196. 【ZJOI2016】线段树 概率期望,动态规划
原文链接www.cnblogs.com/zhouzhendong/p/UOJ196.html 题解 先离散化,设离散化后的值域为 $[0,m]$ . 首先把问题转化一下,变成:对于每一个位置 $i$ ...
- Tableau绘制K线图、布林线、圆环图、雷达图
Tableau绘制K线图.布林线.圆环图.雷达图 本文首发于博客冰山一树Sankey,去博客浏览效果更好.直接右上角搜索该标题即可 一. K线图 1.1 导入数据源 1.2 拖拽字段 将[日期]托到列 ...
- 点线图中的A*算法
A*简介 A*(A-Star)算法是一种启发式算法,是静态路网中求解最短路最有效的方法.公式表示为:f(n)=g(n)+h(n), 其中f(n) 是节点n从初始点到目标点的估价函数, g(n) 是在状 ...
- C#下如何用NPlot绘制期货股票K线图(3):设计要显示的股票价格图表窗口并定义相应类的成员及函数
[内容简介] 上一篇介绍了要显示K线图所需要的数据结构,及要动态显示K线图,需要动态读取数据文件必需的几个功能函数.本篇介绍要显示蜡烛图所用到的窗口界面设计及对应类定义.下面分述如下: [窗口界面] ...
- 利用JFreeChart绘制股票K线图完整解决方案
http://blog.sina.com.cn/s/blog_4ad042e50100q7d9.html 利用JFreeChart绘制股票K线图完整解决方案 (2011-04-30 13:27:17) ...
- IOS 股票K线图、分时图
IOS 股票K线图.分时图,网上开源项目很少,质量也是参差不齐:偶尔搜索到看似有希望的文章,点进去,还是个标题党:深受毒害.经过一段时间的探索,终于在开源基础上完成了自己的股票K线图.分时图: 先放出 ...
随机推荐
- Spring Cloud微服务实战:手把手带你整合eureka&zuul&feign&hystrix
转载自:https://www.jianshu.com/p/cab8f83b0f0e 代码实现:https://gitee.com/ccsoftlucifer/springCloud_Eureka_z ...
- python入门篇
第一篇:python入门 第二篇:数据类型.字符编码.文件处理 第三篇:函数 第四篇:模块与包 第五篇:常用模块 第六篇:面向对象 第七篇:面向对象高级 第八篇:异常处理 第九篇:网络编程 第十篇:并 ...
- 使用Kubeadm部署Kubernetes1.14.1集群
一.环境说明 主机名 IP地址 角色 系统 k8s-node-1 192.170.38.80 k8s-master Centos7.6 k8s-node-2 192.170.38.81 k8s-nod ...
- react图工具集成
背景 调查了react下的图工具库, 并继承到项目中, 经过调研列出如下两个图工具库,可以同时使用. data-ui react-c3js 在一个工具中没有所需的图时候, 可以使用另一个替代. dat ...
- Linux的简单命令
Linux的简单命令 1.更改linux服务器的登录密码 成功登录后输入命令: passwd 然后按照提示操作即可 2.在当前路径下新建文件夹:mkdir 新建文件夹名 3.解压和压缩文件tar.gz ...
- #20175204 张湲祯 2018-2019-2《Java程序设计》第五周学习总结
20175204 张湲祯 2018-2019-2<Java程序设计>第五周学习总结 教材学习内容总结 -第六章接口与实现要点: -接口: 1.使用关键字interface定义接口. 2.接 ...
- hihocoder 1505
hihocoder 1505 题意:给你n个数,让你从n个数中抽两个数,再抽两个数,使得前两个数和后两个数相等 分析:对 i,j,p,q遍历的话时间复杂度会达到o(n4),所以考虑优化p,q 假设分配 ...
- RNN和LSTM
一.RNN 全称为Recurrent Neural Network,意为循环神经网络,用于处理序列数据. 序列数据是指在不同时间点上收集到的数据,反映了某一事物.现象等随时间的变化状态或程度.即数据之 ...
- Shell中字符串的切割、拼接、比较、替换
[截取] 一.Linux shell 截取字符变量的前8位,有方法如下: expr substr “$a” 1 8 : 二.按指定的字符串截取 第一种方法: ${varible##*string} # ...
- 微信小程序开发之自定义菜单tabbar
做这个 遇到问题比较多,特此记录以便查看,直接上代码: 一.app.js 控制原有菜单隐藏.启用新菜单.菜单列表,集中在这里控制 hideTabBar这个很关键,解决苹果6S导致的双导航栏:原文htt ...