2017 ACM-ICPC 亚洲区(西安赛区)网络赛 A Tree 树分治维护矩阵乘积

本文介绍了一种解决特定图论问题的方法,利用矩阵压位技术和点分治策略,处理了图中路径询问的问题。文章详细解释了如何在点分治过程中匹配不同子树中的询问路径端点,并通过维护矩阵乘积来计算答案,特别注意矩阵乘法不满足交换律的特性。此外,还提到了一种优化方案,即使用带权并查集维护矩阵乘积,以减少时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://nanti.jisuanke.com/t/A1267

艹,忘记矩阵乘法不支持交换律了,调了一年。

我们把询问放到询问路径的端点上,然后在点分治的过程中,看不同子树中有没有同一条询问路径的端点,有的话就匹配成功计算出答案。

由于矩阵乘法不支持交换律,所以第i条询问路径的起点u添加i,终点v添加i+q,而且在点分治dfs的过程中,要维护从rt到v的disdw[v],也要维护反向的disup[v],然后再匹配成功的时候判断一下乘的顺序

由于是64位的01矩阵,所以用ull对矩阵进行压位,最后复杂度是nlogn*64*64的,跑了3s,本题时限9s,还算好

似乎还可以直接dfs,然后用带权并查集维护矩阵乘积,少个log

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;

const int maxn=6e3+10;
const int maxl=3e4+10;
const int mod=19260817;

int n,q,cnt,len,top,tlen;
ull sed;
int ehead[maxl],a[maxl],s[maxl],tmp[maxl],ans[maxl],numid[maxl];
int mi19[65],mi26[65];
struct ed
{
	int to,nxt;
}e[maxl<<1];
bool vis[maxl];
struct matrix
{
	ull a[64];
	matrix()
	{
		memset(a,0,sizeof(a));
	}
	inline void clear()
	{
		memset(a,0,sizeof(a));
	}
	inline void init()
	{
		for(int i=0;i<64;i++)
			a[i]=(1ull<<i);
	}
	matrix operator * (const matrix &b)const
	{
		matrix c;
		for(int i=0;i<64;i++)
			for(int j=0;j<64;j++)
			if((a[i]>>j)&1)
				c.a[i]^=b.a[j];
		return c;
	}
}m[maxn],num[maxl],disup[maxn],disdw[maxn],one;
struct centertree
{
	int n,ans,mi;
	int son[maxl];
	inline void dfs(int u,int fa)
	{
		son[u]=1;int v,mx=0;
		for(int i=ehead[u];i;i=e[i].nxt)
		{
			v=e[i].to;
			if(vis[v] || v==fa) continue;
			dfs(v,u);
			son[u]+=son[v];
			mx=max(mx,son[v]);
		}
		mx=max(mx,n-son[u]);
		if(mx<mi)
			mi=mx,ans=u;
	}
	inline int getcenter(int u)
	{
		ans=0;mi=maxl;
		dfs(u,0);
		return ans;
	}
}tree;
set <int> qry[maxl];

inline void add(int u,int v)
{
	e[++cnt].to=v;e[cnt].nxt=ehead[u];ehead[u]=cnt;
}

inline void init()
{
	for(int i=1;i<=n;i++)
	{
		m[i].clear();
		for(int p=0;p<64;p++)
		{
			sed ^=sed * sed +15;
			for(int q=0;q<64;q++)
				m[i].a[p]|=sed&(1ull<<q);
		}
	}
}

inline int trans(matrix x)
{
	int sum=0;
	for(int i=1;i<=64;i++)
		for(int j=1;j<=64;j++)
		if(x.a[i-1]&(1ull<<(j-1)))
			sum=(sum+1ll*mi19[i]*mi26[j]%mod)%mod;
	return sum;
}

inline void prework()
{
	for(int i=1;i<=n;i++)
	{
		ehead[i]=0,vis[i]=false;
		qry[i].clear();
	}
	cnt=0;int u,v;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	scanf("%llu",&sed);
	init();
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&u,&v);
		if(u==v)
		{
			ans[i]=trans(m[u]);
			continue;
		}
		qry[u].insert(i);qry[v].insert(i+q);
	}
}

inline void dfs(int u,int fa)
{
	s[++top]=u;int v;
	for(int i=ehead[u];i;i=e[i].nxt)
	{
		v=e[i].to;
		if(vis[v] || v==fa) continue;
		disdw[v]=disdw[u]*m[v];
		disup[v]=m[v]*disup[u];
		dfs(v,u);
	}
}

inline int opp(int d)
{
	return d>q ? d-q: d; 
}

inline void calc(int u,int fa)
{
	top=0;disdw[u]=m[u];disup[u]=m[u];
	dfs(u,0);int x;
	for(int i=1;i<=top;i++)
	{
		tlen=0;
		for(int d : qry[s[i]])
		if(numid[opp(d)])
		{
			if(d>q)
				ans[opp(d)]=trans(num[opp(d)]*m[fa]*disdw[s[i]]);
			else
				ans[opp(d)]=trans(disup[s[i]]*m[fa]*num[opp(d)]);
			tmp[++tlen]=d;
			x= (d<=q) ? d+q:d-q;
			qry[numid[opp(d)]].erase(qry[numid[opp(d)]].find(x));
		}
		for(int j=1;j<=tlen;j++)
			qry[s[i]].erase(qry[s[i]].find(tmp[j]));
	}
	for(int i=1;i<=top;i++)
		for(int d : qry[s[i]])
		{
			numid[opp(d)]=s[i];
			if(d>q)
				num[opp(d)]=disdw[s[i]];
			else
				num[opp(d)]=disup[s[i]];
			a[++len]=opp(d);
		}
}

inline void solv(int u)
{
	vis[u]=true;int v,rt;
	len=0;
	for(int d : qry[u])
		numid[opp(d)]=u,a[++len]=opp(d),num[opp(d)]=one;
	disdw[u]=disup[u]=one; 
	for(int i=ehead[u];i;i=e[i].nxt)
	{
		v=e[i].to;
		if(vis[v]) continue;
		calc(v,u);
	}
	for(int i=1;i<=len;i++)
		numid[opp(a[i])]=0;
	for(int i=ehead[u];i;i=e[i].nxt)
	{
		v=e[i].to;
		if(vis[v]) continue;
		tree.n=tree.son[v];
		rt=tree.getcenter(v);
		solv(rt);
	}
}

inline void mainwork()
{
	tree.n=n;
	int rt=tree.getcenter(1);
	solv(rt);
}

inline void print()
{
	for(int i=1;i<=q;i++)
		printf("%d\n",ans[i]);
}

int main()
{
	//freopen("out.out","w",stdout);
	one.init();
	mi19[0]=1;mi26[0]=1;
	for(int i=1;i<=64;i++)
	{
		mi19[i]=1ll*mi19[i-1]*19%mod;
		mi26[i]=1ll*mi26[i-1]*26%mod;
	}
	while(~scanf("%d%d",&n,&q))
	{
		prework();
		mainwork();
		print();
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值