poj 1322 Chocolate(递推/矩阵加速/高等代数+概率统计/数学)

poj 1322 Chocolate(递推/矩阵加速/高等代数+概率统计/数学)
总时间限制: 2000ms 内存限制: 65536kB

描述
In 2100, ACM chocolate will be one of the favorite foods in the world.

“Green, orange, brown, red…”, colorful sugar-coated shell maybe is the most attractive feature of ACM chocolate. How many colors have you ever seen? Nowadays, it’s said that the ACM chooses from a palette of twenty-four colors to paint their delicious candy bits.

One day, Sandy played a game on a big package of ACM chocolates which contains five colors (green, orange, brown, red and yellow). Each time he took one chocolate from the package and placed it on the table. If there were two chocolates of the same color on the table, he ate both of them. He found a quite interesting thing that in most of the time there were always 2 or 3 chocolates on the table.

Now, here comes the problem, if there are C colors of ACM chocolates in the package (colors are distributed evenly), after N chocolates are taken from the package, what’s the probability that there is exactly M chocolates on the table? Would you please write a program to figure it out?

输入
The input file for this problem contains several test cases, one per line.

For each case, there are three non-negative integers: C (C <= 100), N and M (N, M <= 1000000).

The input is terminated by a line containing a single zero.

输出
The output should be one real number per line, shows the probability for each case, round to three decimal places.

样例输入
5 100 2
0

样例输出
0.625

来源
Beijing 2002

首先,这个是一个非常裸的数学题,可以很方便的写出一系列递推式。现在问题来了,经过计算很快发现,直接递推一次复杂度为O(c),递推n次复杂度为O(nc),根据题意,nc=10^8,又是多组数据,肯定会超时的。

接下来,我的概率把看作一个向量,用矩阵表示状态转移,接下来我做了一个错误(其实并不错误,这个思路很对,只是我没有具体情况具体分析)的决定,直接快速幂运算。可惜的是,一个稀疏矩阵,明明很有希望求出特征值来做,我却硬生生直接矩阵乘法做,复杂度为O(c^3*lgn),附赠一个很大的常数,有多大呢,写一下快速幂能体会到,就算不考虑常数,也有近10^7数量级,考虑常数比上一种直接做好不了多少甚至更糟,所以就TLE了。(值得庆幸的是,本地测试大数据证明了它的正确性,还是客观上写对了快速幂)

反思一下,战无不胜最常见的快速幂算法为什么在这里阴沟翻船了呢?原因如下:
1.矩阵尺寸没有远远小于递推次数,快速幂的预处理又时间开销很大,而本题一次预处理并不能解决所有组数据,只能做一组数据得不偿失。
2.本题稀疏矩阵的特点没能充分利用,浪费了大量时间。
3.奇偶性没注意到,浪费了一点时间。

那么,快速幂都过不了应该怎么做?有三个可行思路:
1.高等代数方法进一步简化矩阵计算。
2.注意到n比较大时候呈现周期为2的循环了,那么n>1000时候直接令n=n&1+1000。然后快速幂。
3.注意到n比较大时候呈现周期为2的循环了,那么n>1000时候直接令n=n&1+1000。然后直接递推。

在2.3.情况下注意到n在1000左右快速幂花销反而大了,所以2.远远不如3.的时间效率,本题在n很大时候离散模型退化为连续分布了,要注意到这一点题目就可以迎刃而解,否则要么超时TLE,要么精度问题WA。另外,精度问题也令本题有些琐碎甚至不可控…

这个题很值得学习,告诉我们快速幂不一定是万能的,用之前先想一想…不过还是贴一个快速幂(version 1)和标准答案纪念一下。

version 1

Accepted    1048kB  1630ms  1378 B  G++
#include<stdio.h>

double power[10][101][101];
double temp[101][101],ans[101][101];
int c,n,m;

void copy(double target[][101],double a[][101])
{
    for (int i=0;i<=c;i++)
        for (int j=0;j<=c;j++)
            target[i][j]=a[i][j];
    return;
}

void multi_matrix(double product[][101],double a[][101],double b[][101])
{
    for (int i=0;i<=c;i++)
        for (int j=0;j<=c;j++)
            product[i][j]=0.0;
    for (int k=0;k<=c;k++)
        for (int i=0;i<=c;i++)
            for (int j=0;j<=c;j++)
                product[i][j]+=a[i][k]*b[k][j];
    return; 
}

void calculate_power()
{
    for (int i=1;i<10;i++)
        multi_matrix(power[i],power[i-1],power[i-1]);
    return;
}

int main()
{ 
    //freopen("input.txt","r",stdin);
    while (scanf("%d",&c)&&c)
    {
        scanf("%d%d",&n,&m);
        if(m>c||m>n||(m+n)%2)
        {
            printf("0.000\n");
            continue;
        }
        if(n>1000)
        {
            n=1000+n%2;
        } 
        for (int i=0;i<=c;i++)
            for (int j=0;j<=c;j++)
                power[0][i][j]=0;
        for (int i=1;i<=c;i++)
            power[0][i-1][i]=(i*1.0)/c;
        for (int i=1;i<=c;i++)
            power[0][i][i-1]=(c+1-i*1.0)/c;
        calculate_power();
        for (int i=0;i<=c;i++)
            for (int j=0;j<=c;j++)
                ans[i][j]=0;
        for (int i=0;i<=c;i++)
            ans[i][i]=1.0;
        //a(n)=A(n次幂)*a(0)
        for (int e=0;e<10;e++)
            if (n&(1<<e))
            {
                copy(temp,ans);
                multi_matrix(ans,temp,power[e]);
            }
        double sum=0;
        for (int i=0;i<=c;i++)
            sum+=ans[i][0];
        printf("%.3lf\n",ans[m][0]);
    }
    return 0;
}

version 2

Accepted    252kB   90ms    745 B   G++
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<memory.h>

#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;
const int N=105;
double dp[2][N];
int c,n,m;
int main()
{
    //freopen("input.txt","r",stdin); 
    while(scanf("%d%d%d",&c,&n,&m),c!=0)
    {
        memset(dp,0,sizeof(dp));
        if(m>c||m>n||(m+n)%2)
        {
            printf("0.000\n");
            continue;
        }
        if(n>1000)
        {
            n=1000+n%2;
        }
        dp[0][0]=1.0;//n=0,m=0时候输出1.000,看了discuss才知道的,否则一直WA。
        dp[1][1]=1.0;
        for(int i=2;i<=n;i++)
        {
            dp[i%2][0]=dp[(i-1)%2][1]/c;
            dp[i%2][c]=dp[(i-1)%2][c-1]/c;
            for(int j=1;j<=i&&j<c;j++)
            {
                dp[i%2][j]=dp[(i-1)%2][j-1]*(c-j+1.0)/c+dp[(i-1)%2][j+1]*(j+1.0)/c;
            }
        }
        printf("%.3lf\n",dp[n%2][m]);
    }

    return 0;
}

你可能感兴趣的:(poj 1322 Chocolate(递推/矩阵加速/高等代数+概率统计/数学))