【LightOJ 1038】Race to 1 Again(概率DP求期望)
题目大意:
对于数字N,随机选择一个N的约数,并用N除它,得到一个新数,作为N。
这样算1步。变为1则结束。
问期望步数。
复杂度搞错了,绕了个大弯。。。感觉开始渐渐对期望求法有感觉了。
最直接的就是期望设未知为已知,然后变未知为已知。
这题其实找的就是
Vn=∑i=1k(Vk+1)
Vk表示n的第k个约数,当然Vn也包含在约数内
这样移项化简后就变成了
Vn=∑k−1i=1Vkk−1+kk−1
通过预处理,遍历i的所有约数,就可以求出从i出发到达1的步数期望了
约数很少,O(nlogn)预处理
傻了,整的很恐怖……
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 112345;
const int mod = 1e9+7;
const double eps = 1e-8;
double dp[msz];
bool Isp[msz];
int p[msz];
int tp,sz;
int fac[233],fct[233];
double getf(int pos,int mul,double p)
{
if(pos == sz) return dp[mul]*p;
double ans = 0;
for(int i = 0; i <= fct[pos]; ++i)
{
ans += getf(pos+1,mul,p);
mul *= fac[pos];
}
return ans;
}
void init()
{
memset(dp,0,sizeof(dp));
memset(Isp,0,sizeof(Isp));
tp = 0;
for(int i = 2; i <= 100000; ++i)
{
if(!Isp[i])
{
p[tp++] = i;
}
for(int j = 0; j < tp && p[j]*i <= 100000; ++j)
{
Isp[p[j]*i] = 1;
if(i%p[j] == 0) break;
}
int cnt = 1;
int x = i;
sz = 0;
for(int j = 0; j < tp && p[j] <= x; ++j)
{
if(x%p[j]) continue;
fac[sz] = p[j];
fct[sz] = 0;
while(x%p[j] == 0)
{
x /= p[j];
fct[sz]++;
}
cnt = cnt*(fct[sz]+1);
sz++;
}
if(x != 1)
{
fac[sz] = x;
fct[sz++] = 1;
cnt *= 2;
}
dp[i] = getf(0,1,1.0/(cnt-1))+cnt*1.0/(cnt-1);
}
}
int main()
{
//fread("");
//fwrite("");
int t,n;
init();
scanf("%d",&t);
for(int z = 1; z <= t; ++z)
{
scanf("%d",&n);
printf("Case %d: %.11f\n",z,dp[n]);
}
return 0;
}