[NOI2002]Robot(欧拉函数+DP)

计蒜客之提交链接

题意:

——默认你已仔细阅读过题干,并且了解欧拉函数。

简化版题意:

——题目已给出 m 的质因数分解式,找 m 的所有约数,约数可分为3类(政客,军人,学者),每个约数只能代表三者其一。即:所以政客+所有军人+所有学者==m的所有约数
注意:所有约数中不包括1,因为 1 不属于三者中任意一个。

——所求:所有政客的独立数之和,所有军人的独立数之和,所有学者的独立数之和。

质因数分解式就是找约数使用的。

注释:

独立数:
x 的独立数:就是 x 的欧拉函数值。

政客:
能分解成偶数个不同奇质数的乘积.(质因数的指数小于等于1)

军人:
能分解成奇数个不同奇质数的乘积.(质因数的指数小于等于1)

学者:
不满足1、2的约数.(约数1除外,并且该约数较多)

如何解题:

计算 m 所有约数独立数之和ans
计算 m 所有政客独立数之和ans1
计算 m 所有军人独立数之和ans2
所有学者独立数之和=ans-ans1-ans2。

——如何计算 m 所有约数独立数之和?(包括1)

答:该值就是 m 。复杂的证明方式(不建议看),点击它,找到题解的第二行

——计算ans1和ans2。

计算ans1和ans2不考虑指数,且质数2也不用考虑,因为政客和军人的定义。

想象一下:面前摆放着 n 个不同的质数。

挑奇数个质数就表示政客
挑偶数个质数就表示军人

每个质数都有2种选择:选或不选。【dfs或者dp。该题个数较多,只能dp】

奇偶个数相差1,因此二者之间计算有关联,需要用到欧拉函数的积性性质。

计算需要用到欧拉函数性质:

——欧拉函数是积性函数,如果 i,j 互质,那么phi ( i * j ) = phi ( i ) * phi ( j )。

如何得到所有约数的欧拉函数值累加和:dp思想

——用f [ i ] [ j ] 表示选到第 i 个质数一共选了 j 个质数的 phi 的累加和。

比如说:
f [ 5 ] [ 1 ] 表示当选到第5个质数时,只选 1 个质数,
其实我们有5中选择,
即 f [ 5 ]  [ 1 ] = f [ 1 ] [ 1 ] + f [ 2 ] [ 1 ] + f [ 3 ] [ 1 ] + f [ 4 ] [ 1 ] + f [ 5 ] [ 1 ] ,
这里可一用到累加和思想,
即 f [ 5 ] [ 1 ] = f [ 4 ] [ 1 ] + f [ 5 ] [ 1 ]

注意:
——后加的 f [ 5 ] [ 1 ] 仍然是未知的,怎么得到,其实它是从前一个状态 f [ 4 ] [ 0 ] 再选一个 p [ 5 ] 得到的,这里牵涉边界0了,推的式子不是通式。

通式版:
——如何得到 f [ i ] [ j ](此刻仍不是累加和),从前一个状态f [ i - 1 ] [ j - 1 ] 再选一个 p [ i ]就可得到 f [ i ] [ j ]。
根据积性函数 f [ i ] [ j ] = f [ i - 1 ] [ j - 1 ] * ( p [ i ] - 1 )。

所以状态转移方程为:

当 j ! = 1 时
f [ i ] [ j ] = f [ i - 1 ] [ j ] + f [ i - 1 ] [ j - 1 ] * ( p [ i ] - 1 )
当 j == 1 时
f [ i ] [ j ] = ( f [ i - 1 ] [ 1 ] + p [ i ] - 1)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
const ll MOD=10000;
ll p[N],e[N],f[N][N];
ll quickMod(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1)
			ans=ans*x%MOD;
		x=x*x%MOD;
		y>>=1;
	}
	return ans%MOD;//未进入循环&&MOD=1 
}
int main()
{
	ll n,i,j;
	scanf("%lld",&n);
	for(i=1;i<=n;i++)
		scanf("%lld %lld",&p[i],&e[i]); 
	ll ans=1;
	for(i=1;i<=n;i++)
		ans=ans*quickMod(p[i],e[i])%MOD;
	if(p[1]==2)
	{
		for(i=1;i+1<=n;i++)
		p[i]=p[i+1];
		n--;
	}
	memset(f,0,sizeof(f));
	for(i=1;i<=n;i++)
		for(j=1;j<=i;j++)
			if(j==1)	f[i][1]=(f[i-1][1]+p[i]-1)%MOD;
			else		f[i][j]=(f[i-1][j]+f[i-1][j-1]*(p[i]-1)%MOD)%MOD; 
	ll ans1=0,ans2=0,ans3;
	for(i=2;i<=n;i+=2)	ans1+=f[n][i],ans1%=MOD;
	for(i=1;i<=n;i+=2)	ans2+=f[n][i],ans2%=MOD;
	ans3=((ans-1-ans1-ans2)%MOD+MOD)%MOD;
	printf("%lld\n%lld\n%lld\n",ans1,ans2,ans3);
	return 0;
}

下面的dp不对。

memset(f,0,sizeof(f));
for(i=1;i<=n;i++)
	for(j=1;j<=i;j++)
		if(j==1)	f[i][j]=(p[i]-1)%MOD;
		else		f[i][j]=(f[i-1][j-1]*(p[i]-1)%MOD)%MOD; 
//后累加不行 
for(i=1;i<=n;i++)
	for(j=1;j<=i;j++)
		f[i][j]=(f[i-1][j]+f[i][j])%MOD;

这里的f[i][j]不表示累加和,只表示面对第i个时,选j个约数的的欧拉值,突然想起来确实不对,从i里面选j个,本来就有多种选项,不使用累加和,会影响之后的运算,因为 f [ i ][ j ]只有一次被计算的机会。

参考博客1,不用dp
参考博客2,用dp

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值