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;
}

  

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • SpringMVC 工作原理

    SpringMVC 工作原理1.客户端请求提交到DispatcherServlet2.由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller3.DispatcherServlet将请求提交到Controller4.Controller调用业务逻辑处理后,返回ModelAndView5.DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,并将结果显示到客户

    2022年5月14日
    42
  • 煤矿人员定位系统(福利院上班怎么样)

    每个孩子都是祖国的花朵,他们的健康成长是我们的责任.尤其是福利院的孩子,他们被遗弃,孩子的心理已经造成了创伤,此时,孩子们的心理及身体的健康,我们必须实时监控.这样我们就可以对孩子们进行实时监控,随时随地的了解孩子们的生命体征的各项数据.例如,孩子的血压,心跳等等.甚至还可以使用尿湿监测系统.对孩子的生理问题进行监测.福利院人员定位技术背景:福利院人员定位办理体系,将射频识别技术应用于孩子定位办理,别离于每个房间门口和每个楼层的出口以及每栋楼门口和公寓门口…

    2022年4月16日
    51
  • 彻底搞懂0-1背包问题(动态规划)

    彻底搞懂0-1背包问题(动态规划)看了很多网上的博客,发现对于0-1背包问题很多讲的都很专业,初学者学起来还是比较吃力,今天我就用最简单最形象的语言来描述一下0-1背包问题,为什么不能用贪婪算法,而要选择使用动态规划。首先对于0-1背包问题,我们需要知道的是:每一个物品只有1个,要么全拿,要么不拿,最后使得拿到的物品的总价值最大。假如一个小偷有一个可以容纳4千克的背包,但是发现面前只有有3样物品可以偷:台灯(30元,4千克)、音响(20元,3千克)、充电宝(15元,1千克)(价格和重量可能有点奇怪????)。问,小偷能够偷到的物品的

    2022年7月26日
    11
  • 万能游戏框架

    万能游戏框架万能游戏框架 nbsp nbsp nbsp nbsp 论学习方法 nbsp 游戏框架演变过程 nbsp 游戏开发难点 nbsp nbsp nbsp 代码组织架构 资源 网络 3D 数学 热更新 nbsp 拖拽式和查找式 nbsp 单例式 nbsp nbsp nbsp 常用设计模式讲解 nbsp nbsp nbsp nbsp 1 工厂模式 nbsp nbsp nbsp nbsp 2 策略模式 nbsp nbsp nbsp nbsp 3 观察者模式 nbsp nbsp nbsp nbsp 4 单例模式 nbsp nbsp nbsp nbsp 5 代理模式 nbsp nbsp nbsp nbsp 6 多例模式

    2025年12月8日
    7
  • python爬虫入门教程(二):开始一个简单的爬虫

    python爬虫入门教程(二):开始一个简单的爬虫python爬虫入门教程,介绍编写一个简单爬虫的过程。

    2022年6月7日
    47

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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