HDU 4812:D Tree(点分治 + 递推求逆元(模板))

题目大意:一棵 n 个结点的树,每个结点有一个权值,问你是否存在两个点对它们路径上的点值的乘积 mod ( 1 0 6 + 3 ) (10^6 + 3) (106+3) = k,如果有,输出字典序最小的方案。否则输出No solution

题解:由于时间卡得比较紧,用O(n)递推预处理逆元。因为 1 0 6 + 3 10^6 + 3 106+3 是素数,如果已知 a a a,根据 ( a ∗ b ) m o d    1 0 6 + 3 = k (a * b) \mod 10^6 + 3 = k (ab)mod106+3=k,可以用 a 的逆元乘k算出 b。当前分治结点为 u,dis[v] 为 v 到 u 的路径点权乘积,用map 存一下dis 对应的点 v,暴力合并更新答案。

(可能是我用了vector的原因,加上测试样例是多组,常数非常大)
复杂度 O ( n ∗ l o g n ∗ l o g n ) O(n * logn * logn) O(nlognlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pii pair<int,int>
#define fir first
#define sec second 
const int inf = 0x3f3f3f3f;
int n,k;
const int maxn = 1e6 + 500;
const int p = 1e6 + 3;
vector<int> g[maxn];
pii ans;
int mul(int a,int b) {
	return (int)((1ll * a * b) % p); 
}
bool cmp(pii a,pii b) {
	return a.fir == b.fir ? a.sec < b.sec : a.fir < b.fir;
}
map<int,int> pot;
vector<int> t,ft;
int a[maxn],root,sz[maxn],tot,f[maxn],inv[maxn * 10];
int dis[maxn];
bool done[maxn];
void initInv() {
    inv[0] = inv[1] = 1;
    for (int i = 2; i < p; i++) {
        inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
    }
}
inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
void getroot(int u,int fa) {
	f[u] = 0;sz[u] = 1;
	for(auto it : g[u]) {
		if(it == fa || done[it]) continue;
		getroot(it,u);
		sz[u] += sz[it];
		f[u] = max(f[u],sz[it]);
	}
	f[u] = max(f[u],tot - sz[u]);
	if(f[u] < f[root] || !root) root = u;
}
void dfs(int u,int fa) {
	int d = dis[u];
	t.push_back(u);
	for(auto it : g[u]) {
		if(it == fa || done[it]) continue;
		dis[it] = mul(dis[u],a[it]);
		dfs(it,u);
	}
}
void solve(int u) {
	done[u] = true;
	pot.clear();pot[a[u]] = u;
	for(auto it : g[u]) {
		if(done[it]) continue;
		t.clear();
		dis[it] = mul(a[it],a[u]);
		dfs(it,u);
		for(auto i : t) {
			int x = mul(dis[i],inv[a[u]]);
			int p = mul(inv[x],k);
			if(pot[p]) {
				int u = pot[p],v = i;
				if(u > v) swap(u,v);
				if(ans.fir > u || (ans.fir == u && ans.sec > v)) ans = pii(u,v);
			}
		}
		for(auto i : t)
			if(!pot[dis[i]] || pot[dis[i]] > i) pot[dis[i]] = i;
	}
}
void divide(int u) {
	solve(u);
	for(auto it : g[u]) {
		if(done[it]) continue;
		tot = sz[it];root = 0;
		getroot(it,-1);divide(root);
	}
}
int main() {
	initInv();
	while(scanf("%d%d",&n,&k) != EOF) {
		for(int i = 1; i <= n; i++) {
			a[i] = read();
			g[i].clear();done[i] = false;
		}
		for(int i = 1; i < n; i++) {
			int u,v;
			u = read();v = read();
			g[u].push_back(v);
			g[v].push_back(u);
		}	
		ans = pii(inf,inf);
		root = 0;tot = n;getroot(1,-1);
		divide(root);
		if(ans.fir != inf && ans.sec != inf)
			printf("%d %d\n",ans.fir,ans.sec);
		else puts("No solution");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值