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

UOJ#373. 【ZJOI2018】线图 搜索,树哈希,动态规划原文链接www.cnblogs.com/zhouzhendong/p/UOJ373.html前言真是一道毒瘤题。UOJ卡常毒瘤++。我卡了1.5h的常数才过QAQOrzjry标算居然是指数做法

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

原文链接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;
}

  

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/167034.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ubuntu sublime安装及配置

    ubuntu sublime安装及配置

    2021年11月29日
  • redis主从同步方式(redis数据同步原理)

    1.前言在redis中为了保证redis的高可用,一般会搭建一种集群模式就是主从模式。主从模式可以保证redis的高可用,那么redis是怎么保证主从服务器的数据一致性的,接下来我们浅谈下redis主(master)从(slave)同步的原理。2.初次全量同步当一个redis服务器初次向主服务器发送salveof命令时,redis从服务器会进行一次全量同步,同步的步骤如下图所示:s…

  • LeetCode–046–全排列(java)

    LeetCode–046–全排列(java)给定一个没有重复数字的序列,返回其所有可能的全排列。示例:无奈,用swap的方法从左向右滑动,直到最后结果和最初的一致停止,只适用于三位数。。。。(改进一下让每个数字作为第一位后面的进行滑动,应该

  • FC游戏 《三国志2-霸王的大陆》攻略「建议收藏」

    FC游戏 《三国志2-霸王的大陆》攻略「建议收藏」《三国志2-霸王的大陆》是日本南梦宫公司研发的一款历史战略模拟游戏,于1992年06月10日在红白机平台上发行。在开始游戏选择君主时(一定要在君主未出现前的画面时进行第二步),按住1P的START不要放,按住START同时,连续依次按上,下,左,右,按满3次,听到“乒”一下的声音后再开始游戏,这时再选君主:君主城金钱、兵马、宝等全满。一、武将1)武将出场时间189年-190…

    2022年10月22日
  • 超详细的MySQL8.0.20安装教程及其安装问题处理

    超详细的MySQL8.0.20安装教程及其安装问题处理文章目录:官网下载MySQL的安装包添加并配置my.ini文件配置系统变量并初始化MySQL安装并启动MySQLNavicat连接MySQL并修其密码安装过程中常见问题及其解决方法官网下载MySQL的安装包下载链接如下:MySQL8.0.20版本其他版本:MySQL8.0.16版本MySQL8.0.20版本压缩包解压后如下图所示:添加并配置my.ini文件在原解压根…

  • 以太坊(ETH)挖矿最新教程_以太坊个人挖矿

    以太坊(ETH)挖矿最新教程_以太坊个人挖矿以太坊(ETH)挖矿最新教程目前网上的大多数以太坊挖矿教程是很早整理编写的,基本都已过时不适用了。但挖矿是一个技术持续升级更新的过程,因此根据最新实战情况编写了本文,并争取在以后抽时间持续保持跟进更新。一.基本要求ETH 挖矿主要是使用显卡来挖矿。因此你需要一台拥有以下设备的PC(矿机):显卡、主板、电源、CPU、内存(推荐4G)、硬盘(推荐60G的SSD)、PCI-E转接线

    2022年10月15日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号