Codeforce 1293 F. Chaotic V.(思维 + 虚树DP + 数论)

在这里插入图片描述


题意:有一个无穷个点的无向图,第 x 个点有个点权 x,x 会连一条无向边到 x f ( x ) \frac{x}{f(x)} f(x)x,其中 f(x) 是 x 的最小质因子。
输入 n 个关键点( n ≤ 1 0 6 n \leq 10^6 n106),它们的标号 是 k i ! , k i ≤ 5000 k_i!,k_i\leq5000 ki!ki5000,设这些关键点到 p 的距离和为 s u m sum sum,求 s u m sum sum 最小值。


观察一下会发现 无穷大的无向图就是一棵无穷大的树,问题转化成在这样一棵无穷大的树上要求一个仅考虑关键点权重的重心,而关键点至多只有 5000 个。

考虑虚树,将关键点提取出来建树进行 dp。

最后重心点一定在虚树的点上,因为不在虚树上的点完全没有贡献,考虑重心从根节点移动:若重心能移动到 u,v 的边上的某个不在虚树上的点,则重心一定能移动到 v,最后重心点一定落在虚树的某个点上。

建立虚树需要按原树的 dfs 序进行建树,过程中还要求两点的 lca。

先考虑如何得到 dfs 序。

由于原图是一个无穷大的树,绝不可能在原图上 dfs 得到每个节点的 dfs 序。观察一下树的结构,1 号 节点是根节点,每个节点 u 的子节点是当前节点乘上一个质因子 p,这个质因子的值 p ≤ f ( u ) p \leq f(u) pf(u)

在这里插入图片描述

如果 dfs 先走质因子较大的那条边,那么可以得到一个可以比较两个点间 dfs 序的方法:从大到小比较 u,v的每一个质因子:

若质因子不同,则质因子较大的 dfs 序较小
若质因子相同,但幂次不同:有三种情况 :
1.若该质因子都不是 u,v 的最小质因子,那么一定是 幂次更大的 dfs 序更小(因为 dfs 时先走 质因子较大的边)。
2.若该质因子都是 u,v 的最小质因子,那么幂次更小的 dfs 序更小
3.若该质因子是 u (但不是 v)或 是v(但不是 u)的最小质因子,那么是谁的最小质因子,谁的 dfs 序就更小。

接下来考虑如何得到 lca

一个节点的父节点是该节点去掉它的最小质因子,那么两个节点的 lca 如何求:
先预处理得到 u,v 的所有质因子及其幂次,从大到小依次遍历,

若当前质因子 p u ≠ p v p_u \neq p_v pu=pv,遍历结束。
若当前质因子 p u = p v p_u = p_v pu=pv,考虑它们的幂次 m u , m v m_u,m_v mumv 的关系:
m u ≠ m v m_u \ne m_v mu=mv,取较小的一个幂次,当前已求得的 lca 的标号乘上 p m p^m pm,结束遍历
m u ≠ m v m_u \ne m_v mu=mv,当前已求得 的 lca 的标号 乘上 p m p^m pm,继续遍历下一个质因子

显然不可能对这么大的数进行相乘得到它们的点的下标,注意到输入的权值 k 小于 5000,考虑用每个数的质因子分解形式来表示这个数。预处理出[1,5000] 所有数字的阶乘的质因子分解形式,将它们的质因子及其幂次存到 vector 里,使用 map 或 hash 进行映射。

具体见代码:


(使用 hash + map 映射,有被hack的概率)代码:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
const ll mod = 1610612741ll;
const int maxn = 1e6 + 10;
int n,top,sz,k[maxn],val[maxn],np[maxn];
map<ll,int> mp;
vector<pii> sta[maxn],a[maxn];
vector<pii> t[5010];
bool ispri[maxn];
int pri[maxn],vis[maxn];
vector<pii> g[maxn];
ll dp[maxn],sum[maxn];
int Hash(vector<pii> x) {
	ll p = 1;
	for(auto it : x) {
		p = (p * 131 % mod + it.fir) % mod;
		p = (p * 131 % mod + it.sec) % mod;
	}
	return p;
}
void sieve(int n) {
	ispri[0] = ispri[1] = true;
	pri[0] = 0;
	for(int i = 2; i <= n; i++) {
		if(!ispri[i]) pri[++pri[0]] = i;
		for(int j = 1; j <= pri[0] && i * pri[j] <= n; j++) {
			ispri[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
		}
	}
}
void init() {
	for(int i = 1; i <= 5000; i++) {		//分解 i! 
		for(int j = 1; pri[j] <= i && j <= pri[0]; j++) {
			int x = i,cnt = 0;
			while(x) {
				cnt += x / pri[j];
				x /= pri[j];	
			}
			t[i].push_back(pii(pri[j],cnt));
		}
		reverse(t[i].begin(),t[i].end());		//从大到小 
		np[i] = mp[Hash(t[i])] = ++sz;
	}
}
int cmp(vector<pii> x,vector<pii> y) {
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) {
			return x[i].fir > y[i].fir ? 1 : -1;
		} else if(x[i].sec != y[i].sec) {
			if(i == x.size() - 1 && i != y.size() - 1)
				return 1;
			else if(i != x.size() - 1 && i == y.size() - 1)
				return -1;
			else if(i == x.size() - 1 && i == y.size() - 1) {
				return 	x[i].sec > y[i].sec ? 1 : -1;
			} else {
				return 	x[i].sec < y[i].sec ? 1 : -1;
			}
		}
	}
	if(x.size() < y.size()) return 1;
	else if(x.size() == y.size()) return 0;
	else return -1;
}
int cmp2(vector<pii> x,vector<pii> y) {
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) {
			return x[i].fir > y[i].fir;
		} else if(x[i].sec != y[i].sec) {
			if(i == x.size() - 1 && i != y.size() - 1)
				return 1;
			else if(i != x.size() - 1 && i == y.size() - 1)
				return 0;
			else if(i == x.size() - 1 && i == y.size() - 1)
				return 	x[i].sec < y[i].sec;
			else 
				return x[i].sec > y[i].sec;
		}
	}
	return x.size() < y.size();
}
int cal(vector<pii> x,vector<pii> y) {					//计算 x 和 y 边权
	int cnt = 0;
 	for(int i = 0; i < x.size(); i++) {
 		if(i >= y.size())	cnt += x[i].sec;
 		else cnt += x[i].sec - y[i].sec;
	}	
	return cnt;
}
vector<pii> getlca(vector<pii> x,vector<pii> y) {
	vector<pii> g;
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) break;
		else if(x[i].sec != y[i].sec){
			g.push_back(pii(x[i].fir,min(x[i].sec,y[i].sec)));
			break;
		} else {
			g.push_back(pii(x[i].fir,x[i].sec));
		}
	}
	if(!g.size()) return t[1];
	return g;
}
void insert(vector<pii> x) {
	vector<pii> lca = getlca(x,sta[top]);
	while(top > 1 && cmp(lca,sta[top - 1]) >= 0) {
		int u = Hash(sta[top - 1]),v = Hash(sta[top]);
		if(!mp[u]) mp[u] = ++sz;
		if(!mp[v]) mp[v] = ++sz;
		u = mp[u],v = mp[v];
		g[u].push_back(pii(v,cal(sta[top],sta[top - 1])));
		top--;
	}
	if(lca != sta[top]) {
		int u = Hash(lca),v = Hash(sta[top]);
		if(!mp[u]) mp[u] = ++sz;
		if(!mp[v]) mp[v] = ++sz;
		u = mp[u],v = mp[v];
		g[u].push_back(pii(v,cal(sta[top],lca)));
		sta[top] = lca;
	}
	sta[++top] = x;
}
void build() {
	vector<pii> q[5010];
	for(int i = 1; i <= 5000; i++)
		q[i] = t[i];
	sort(q + 1,q + 5000 + 1,cmp2);
	sta[top = 1] = t[1];
	for(int i = q[1] == t[1] ? 2 : 1; i <= 5000; i++)
		insert(q[i]);
	for(int i = 1; i < top; i++) {
		int u = Hash(sta[i]),v = Hash(sta[i + 1]);
		if(!mp[u]) mp[u] = ++sz;
		if(!mp[v]) mp[v] = ++sz;
		u = mp[u],v = mp[v];
		g[u].push_back(pii(v,cal(sta[i + 1],sta[i])));
	}
}
void dfs1(int u) {
	dp[u] = 0,sum[u] = val[u];
	for(auto it : g[u]) {
		dfs1(it.fir);
		sum[u] += sum[it.fir];
		dp[u] += dp[it.fir] + sum[it.fir] * it.sec;
	}
}
void dfs2(int u,ll &ans) {
	for(auto it : g[u]) {
		dp[it.fir] += dp[u] - dp[it.fir] - sum[it.fir] * it.sec;
		dp[it.fir] += (sum[u] - sum[it.fir]) * it.sec;
		sum[it.fir] = sum[u];
		dfs2(it.fir,ans);
	}
	ans = min(ans,dp[u]);
}
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int main() {
	sieve(5000);
	init();
	build();						//建立虚树 
	n = read();
	int tot = 0;
	for(int i = 1,x; i <= n; i++) {
		x = read();
		if(x == 0) x++;
		val[x]++;
	}
	ll ans = 1e15;
	dfs1(1);
	dfs2(1,ans);
	printf("%lld\n",ans);
	return 0;
}

(使用 map 映射)代码

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
const int maxn = 1e6 + 10;
int n,top,sz,k[maxn],val[maxn];
map<vector<pii>,int> mp;
vector<pii> sta[maxn],a[maxn];
vector<pii> t[5010];
bool ispri[maxn];
int pri[maxn],vis[maxn];
vector<pii> g[maxn];
ll dp[maxn],sum[maxn];
void sieve(int n) {
	ispri[0] = ispri[1] = true;
	pri[0] = 0;
	for(int i = 2; i <= n; i++) {
		if(!ispri[i]) pri[++pri[0]] = i;
		for(int j = 1; j <= pri[0] && i * pri[j] <= n; j++) {
			ispri[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
		}
	}
}
void init() {
	for(int i = 1; i <= 5000; i++) {		//分解 i! 
		for(int j = 1; pri[j] <= i && j <= pri[0]; j++) {
			int x = i,cnt = 0;
			while(x) {
				cnt += x / pri[j];
				x /= pri[j];	
			}
			t[i].push_back(pii(pri[j],cnt));
		}
		reverse(t[i].begin(),t[i].end());		//从大到小 
		mp[t[i]] = ++sz;
	}
}
int cmp(vector<pii> x,vector<pii> y) {
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) {
			return x[i].fir > y[i].fir ? 1 : -1;
		} else if(x[i].sec != y[i].sec) {
			if(i == x.size() - 1 && i != y.size() - 1)
				return 1;
			else if(i != x.size() - 1 && i == y.size() - 1)
				return -1;
			else if(i == x.size() - 1 && i == y.size() - 1) {
				return 	x[i].sec > y[i].sec ? 1 : -1;
			} else {
				return 	x[i].sec < y[i].sec ? 1 : -1;
			}
		}
	}
	if(x.size() < y.size()) return 1;
	else if(x.size() == y.size()) return 0;
	else return -1;
}
int cmp2(vector<pii> x,vector<pii> y) {
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) {
			return x[i].fir > y[i].fir;
		} else if(x[i].sec != y[i].sec) {
			if(i == x.size() - 1 && i != y.size() - 1)
				return 1;
			else if(i != x.size() - 1 && i == y.size() - 1)
				return 0;
			else if(i == x.size() - 1 && i == y.size() - 1)
				return 	x[i].sec < y[i].sec;
			else 
				return x[i].sec > y[i].sec;
		}
	}
	return x.size() < y.size();
}
int cal(vector<pii> x,vector<pii> y) {					//计算 x 和 y 边权
	int cnt = 0;
 	for(int i = 0; i < x.size(); i++) {
 		if(i >= y.size())	cnt += x[i].sec;
 		else cnt += x[i].sec - y[i].sec;
	}	
	return cnt;
}
vector<pii> getlca(vector<pii> x,vector<pii> y) {
	vector<pii> g;
	for(int i = 0; i < min(x.size(),y.size()); i++) {
		if(x[i].fir != y[i].fir) break;
		else if(x[i].sec != y[i].sec){
			g.push_back(pii(x[i].fir,min(x[i].sec,y[i].sec)));
			break;
		} else {
			g.push_back(pii(x[i].fir,x[i].sec));
		}
	}
	if(!g.size()) return t[1];
	return g;
}
void insert(vector<pii> x) {
	vector<pii> lca = getlca(x,sta[top]);
	if(!mp[lca]) mp[lca] = ++sz;
	while(top > 1 && cmp(lca,sta[top - 1]) >= 0) {
		if(!mp[sta[top - 1]]) mp[sta[top - 1]] = ++sz;
		if(!mp[sta[top]]) mp[sta[top]] = ++sz;
		int u = mp[sta[top - 1]],v = mp[sta[top]];
		g[u].push_back(pii(v,cal(sta[top],sta[top - 1])));
		top--;
	}
	if(lca != sta[top]) {
		int u = mp[lca],v = mp[sta[top]];
		g[u].push_back(pii(v,cal(sta[top],lca)));
		sta[top] = lca;
	}
	sta[++top] = x;
}
void build() {
	vector<pii> q[5010];
	for(int i = 1; i <= 5000; i++)
		q[i] = t[i];
	sort(q + 1,q + 5000 + 1,cmp2);
	sta[top = 1] = t[1];
	for(int i = q[1] == t[1] ? 2 : 1; i <= 5000; i++)
		insert(q[i]);
	for(int i = 1; i < top; i++) {
		if(!mp[sta[i]]) mp[sta[i]] = ++sz;
		if(!mp[sta[i + 1]]) mp[sta[i + 1]] = ++sz;
		int u = mp[sta[i]],v = mp[sta[i + 1]];
		g[u].push_back(pii(v,cal(sta[i + 1],sta[i])));
	}
}
void dfs1(int u) {
	dp[u] = 0,sum[u] = val[u];
	vis[u] = 1;
	for(auto it : g[u]) {
		if(vis[it.fir]) continue;
		dfs1(it.fir);
		sum[u] += sum[it.fir];
		dp[u] += dp[it.fir] + sum[it.fir] * it.sec;
	}
}
void dfs2(int u,ll &ans) {
	vis[u] = 1;
	for(auto it : g[u]) {
		if(vis[it.fir]) continue;
		dp[it.fir] += dp[u] - dp[it.fir] - sum[it.fir] * it.sec;
		dp[it.fir] += (sum[u] - sum[it.fir]) * it.sec;
		sum[it.fir] = sum[u];
		dfs2(it.fir,ans);
	}
	ans = min(ans,dp[u]);
}
int main() {
	sieve(5000);
	init();
	build();						//建立虚树 
	scanf("%d",&n);
	int tot = 0;
	for(int i = 1,x; i <= n; i++) {		//注意这个地方不要每次都暴力查找 map 或 hash,否则会T
		scanf("%d",&x);
		if(x == 0) x++;
		val[x]++;
	}
	ll ans = 1e15;
	dfs1(1);
	memset(vis,0,sizeof vis);
	dfs2(1,ans);
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值