ZOJ 3824 Fiber-optic Network ~莫比乌斯

本文介绍了一种优化算法,用于计算具有特定性质的子树方案数。通过分解边并利用莫比乌斯函数,实现对每个节点贡献的高效计算。通过剪枝避免重复计算,提高算法效率。
若先确定父结点的取值为A
  设f(i)为子结点取值和A的gcd为i,且子结点为根的子树相邻结点都互质的方案数
  设F(i)为子结点取值和A的gcd为i的倍数,且子结点为根的子树相邻结点都互质的方案数
  则f(1) = sigma(mu[d] * F(d)) 条件 d是A的因子
想到,确定一个父结点,一个子结点,就能确定一棵子树,于是我将每条边分解成两条有向边。数组F[e][j]记录的是有向边e起点作为父结点,以终点为根结点,且根结点取值为j的倍数的方案数
每个结点对父亲取值为A的贡献是sigma(mu[d] * F(d))(d是A的因子)
每个结点的方案数就是每个取值的子结点贡献的乘积再求和

直接枚举每个点,求F[][]的值,需要剪枝,若这条有向边被访问过,说明这颗子树已经求过了。


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
#define rep(i,n) for(int i=0; i= N) break;
			use[i*pri[j]] = 1;
			if(i % pri[j] == 0)
			{
				mu[i*pri[j]] = 0;
				break;
			}
			mu[i*pri[j]] = -mu[i];
		}
	}
}

void Cal(int fa,int u)
{
	int tt = g[fa][u]; // 有向边编号
	if(fa != u && pa[tt]) return;
	pa[tt] = 1;
	for(int i=L[u]; i<=R[u]; i++) cnt[u][i] = 1;
	repe(v,n) if(g[u][v] && v != fa)
	{
		Cal(u,v);
		mst(temp,0);
		tt = g[u][v];
		repe(i,R[u])
		{
			for(int j=i; j<=R[u]; j+=i)
			{
				temp[j] = (temp[j] + mu[i] * F[tt][i] % MOD) % MOD;
			}
		}
		// temp 暂时记录这个子结点对当前结点的各个值的贡献
		for(int i=L[u]; i<=R[u]; i++) if(temp[i]) cnt[u][i] = cnt[u][i] * temp[i] % MOD;
		// cnt[u][i] 记录u结点取值i的方案数
	}
	tt = g[fa][u];
	mst(F[tt],0);
	for(int i=R[u]; i>=1; i--)
	{
		for(int j=(L[u] + i - 1) / i * i; j<=R[u]; j+=i)
		{
			F[tt][i] += cnt[u][j];
			F[tt][i] %= MOD;
		}
	}
}

int main()
{
	//freopen("out.txt","w",stdout);
	
	mobius();
	int T,a,b;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		repe(i,n) scanf("%d",L+i);
		repe(i,n) scanf("%d",R+i);
		mst(g,0);
		rep(i,n-1)
		{
			//a = i + 1;b = i + 2;
			scanf("%d%d",&a,&b);
			g[a][b] = 2 * i + 1;
			g[b][a] = 2 * i + 2;
		}
		mst(pa,0);
		for(int i=1; i<=n; i++)
		{
			Cal(i,i);
		}
		ll cnt[N];
		for(int i=1; i<=n; i++)
		{
			for(int j=L[i]; j<=R[i]; j++) cnt[j] = 1;
			for(int j=1; j<=n; j++) if(g[i][j])
			{
				mst(temp,0);
				for(int d=1; d<=R[i]; d++)
				{
					for(int o=d; o<=R[i]; o+=d)
					{
						temp[o] = (temp[o] + mu[d] * F[g[i][j]][d] % MOD) % MOD;
					}
				}
				for(int d=L[i]; d<=R[i]; d++) cnt[d] = cnt[d] * temp[d] % MOD;
			}
			ll ans = 0;
			for(int j=L[i]; j<=R[i]; j++) ans = (ans + cnt[j] * j % MOD) % MOD;
			ans = (ans + MOD) % MOD;
			if(i > 1) printf(" ");
			printf("%lld",ans);
		}printf("\n");
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值