点分治问题 ----------- HDU4812 D Tree [点分治 + 乘法逆元]

该博客主要探讨了一种解决树上路径权值乘积模特定值问题的算法。通过数学公式推导,利用点分治的方法,找到使得路径权值乘积模1e6+3等于给定值k的路径。博主指出,在更新子树大小时,使用特定的节点存储方式可以避免超时错误。并提供了完整的C++代码实现,包括点分治、快速幂运算以及字典序最小路径查找等关键步骤。在处理多组数据时,注意优化初始化过程,只在需要时初始化对应模数的最小节点。
摘要由CSDN通过智能技术生成

题目链接


题目大意:

给你一颗树,树上节点有个权值 a i a_i ai,现在问你这颗树上是否存在一条路径这个路径上面权值的乘积模1e6+3等于k。
如果有多组答案输出字典序最小的答案


解题思路:

1.首先我们知道
a × b % m o d = k a\times b\%mod=k a×b%mod=k
a = ( k × i n v [ b ] ) a=(k\times inv[b]) a=(k×inv[b])
2.知道这个公式后我们就可以直接点分治了
为了字典序最小,我们要把每次模数对应的最小的结点存下面,但是不能每次都暴力初始化,用到那个数值才初始化哪个。
3.点分每次更新子树大小的时候
now_node = siz[it] > siz[u] ? now_node - siz[it] : siz[u];
会TLE!!!!!


代码:

#pragma comment(linker,"/STACK:102400000,102400000")
#pragma GCC optimize(2)
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
const int mod = 1e6 + 3;
vector<int> G[maxn];
int N, K, val[maxn];
//..................
int root, Mx, now_node;
int max_son[maxn], siz[maxn];
bool vis[maxn];
ll qmi(ll a, ll b) {
	ll res = 1;
	while(b) {
		if(b & 1) res = (a * res) % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
void getroot(int u, int fa) {
   siz[u] = 1;
   max_son[u] = 0;
   for(int i = 0; i < G[u].size(); ++ i) {
	  int it = G[u][i];
	  if(vis[it] || fa == it) continue;
	  getroot(it,u);
	  max_son[u] = max(max_son[u],siz[it]);
	  siz[u] = siz[it] + siz[u];
   }
   max_son[u] = max(max_son[u],now_node - siz[u]);
   if(max_son[u] < Mx) Mx = max_son[u], root = u;
}
pair<int,int> ans;
pair<int,ll> tmp[maxn];
int mul[mod+10], idx;//存对于同一个模数的最小序号
ll lis[maxn], lis_idx;
void dfs(int u, int fa, ll sum) {
	int t = (1ll*K*qmi(sum,mod-2))%mod;
	if(mul[t] != 1e9) {
		if(ans > (pair<int,int>){min(mul[t],u),max(mul[t],u)})
			ans = {min(mul[t],u),max(mul[t],u)};
	}

   tmp[idx++] = {u,sum};
   for(int i = 0; i < G[u].size(); ++ i) {
	   int it = G[u][i];
	   if(it == fa || vis[it]) continue;
       dfs(it, u, sum*val[it]%mod); 
   }
}

inline void getans(int u) {
    for(int i = 0;i < lis_idx; ++ i) mul[lis[i]] = 1e9;
	lis_idx = 0;
	mul[val[u]] = u;
	lis[lis_idx++] = val[u];
    for(int i = 0; i < G[u].size(); ++ i) {
		int it = G[u][i];
        if(vis[it]) continue;
		dfs(it, u, val[it] % mod);
		for(int i = 0; i < idx; ++ i) {
		   int t = 1ll * tmp[i].second * val[u] % mod;
		   mul[t] = min(mul[t],tmp[i].first);
		   lis[lis_idx++] = t;
		}
		idx = 0;
	}
}
void Div(int u) {
	vis[u] = 1;
	getans(u);
    for(int i = 0; i < G[u].size(); ++ i) {
		int it = G[u][i];
		if(vis[it]) continue;
        Mx = 1e9, root = 0;
		now_node = siz[it];
		getroot(it,0);
		Div(root);
	}
}

int main() {
	for(int i = 0; i < mod; ++ i) mul[i] = 1e9;
	while(scanf("%d%d",&N,&K) != EOF) {	
		ans = {1e9,1e9};
		for(int i = 1; i <= N; ++ i) vis[i] = 0, G[i].clear();
		for(int i = 1; i <= N; ++ i)
		    scanf("%d",&val[i]);
		for(int i = 1; i < N; ++ i) {
			int l, r;
			scanf("%d%d",&l,&r);
			G[l].push_back(r);
			G[r].push_back(l);
		} 
		if(K == 0) {
			printf("No solution\n");
			continue;
		}
		now_node = N;
        Mx = 1e9, root = 0;
		getroot(1,0);
		Div(root);
		if(ans.second == 1e9) printf("No solution\n");
		else printf("%d %d\n",ans.first,ans.second);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值