SRM540-div1-2-RandomColoring


题目大意:
     N个栅栏按照标号0,1,...,N-1围成一个圈,从0号栅栏开始染色。每一种颜色用(R,G,B)三原色表示,并且0<=R<maxR, 0<=G<maxG, 0<=B<maxB。规定相邻的两个栅栏颜色必须符合以下的颜色过渡条件:
  • 两种颜色的对应R,G,B差值全部都小于等于d2
  • 两种颜色的对应R,G,B差值至少有一个大于等于d1
将0号栅栏染色为(startR, startG, startB), 然后按编号逐一染色,每次选择颜色时都是考虑前一个栅栏的颜色,随机等概率从所有符合过渡条件的颜色中挑选。问当完成N-1号栅栏的染色时,N-1与0之间不符合颜色过渡条件的概率是多少。
     数据规模:N为[2,40], maxR,maxG,maxB均为[1,50], d1, d2均为[0, 50]

思路:
     这道题比较明显可以看出来是一道动态规划题,按照序号每次考虑一个栅栏被染成所有可能颜色的概率,状态变量为三原色取值r、g、b,一直计算到N-1栅栏,然后将不符合与0栅栏之间颜色过渡条件的那些颜色的概率相加即可。
     从上面的分析看,动态规划的思路非常清晰,但是这道题的难点在于如何高效的实现状态间的转移。
     用double[][][] dp表示当前栅栏所有颜色取值的概率值,double[][][] next表示下一个栅栏所有颜色取值的概率值。把每一种颜色的三原色(r,g,b)看作是三维空间中的一个点。对于当前栅栏的任意一种颜色(r,g,b),其概率值dp[r][g][b]将会均匀分配到next中符合过渡条件的那些颜色上,这个过程可以看作是颜色(r,g,b)概率值扩散的一个过程。对于颜色(r,g,b)而言,其概率所能扩散的范围受过渡条件限制,与当前的状态无关,我们用rSize[r][g][b]表示其所能扩散的范围,那么每一个扩散出来的点拥有dp[r][g][b]/rSize[r][g][b]的概率值(称为扩散概率值)。如果正向的累加next中每一种颜色的取值,那么计算复杂度为O(maxR*maxG*maxB)种当前颜色乘以O(maxR*maxG*maxB)种下一栅栏颜色,显然无法满足题目给定的数据规模要求。
     我们需要从反向考虑问题,考虑下一栅栏的每一种(r,g,b)颜色的概率值next[r][g][b]由当前栅栏的哪些颜色的概率扩散形成。对于颜色(r,g,b),根据颜色过渡条件,其所有扩散源颜色在三维空间中可以表示成一个边长为(2*d2+1)的立方体,中间挖去边长为(2*d1-1)的立方体。显然,我们不可能把所有扩散源的扩散概率值累加起来,这样不会降低计算的复杂度。优化方案为对这些扩散概率值进行预处理,令sum[r+1][g+1][b+1]表示当前栅栏颜色空间中由(0,0,0)和(r,g,b)所表示的长方体内所有扩散概率值的加和,计算这样的sum数组只需要O(maxR*maxG*maxB)复杂度。根据sum数组,可以O(1)复杂度计算出三维空间中所有立方体的扩散概率值累加和,所以每一个next[r][g][b]计算复杂度都是O(1)。因此,每一个栅栏的颜色空间概率计算包含了两个O(maxR*maxG*maxB)的过程。所有N个栅栏的总时间复杂度为O(N*maxR*maxG*maxB),符合题目数据规模要求。


Java代码:
public class RandomColoring {
    int mR, mG, mB;
    private int calcSize(int r0, int g0, int b0, int r1, int g1, int b1) {
        r0 = Math. max(r0, 0);
        g0 = Math. max(g0, 0);
        b0 = Math. max(b0, 0);
        r1 = Math. min(r1, mR - 1);
        g1 = Math. min(g1, mG - 1);
        b1 = Math. min(b1, mB - 1);
        return (r1 - r0 + 1) * (g1 - g0 + 1) * (b1 - b0 + 1);
    }
    private double calcPropSum(int r0, int g0, int b0, int r1, int g1, int b1, double [][][] sum) {
        r0 = Math. max(r0, 0);
        g0 = Math. max(g0, 0);
        b0 = Math. max(b0, 0);
        r1 = Math. min(r1, mR - 1);
        g1 = Math. min(g1, mG - 1);
        b1 = Math. min(b1, mB - 1);
        return sum[r1 + 1][g1 + 1][b1 + 1] - sum[r0][g1 + 1][b1 + 1]
                - sum[r1 + 1][g0][b1 + 1] - sum[r1 + 1][g1 + 1][b0]
                + sum[r0][g0][b1 + 1] + sum[r0][g1 + 1][b0]
                + sum[r1 + 1][g0][b0] - sum[r0][g0][b0];
    }
    public double getProbability(int N, int maxR, int maxG, int maxB, int startR, int startG,
                                 int startB, int d1, int d2) {
        mR = maxR;
        mG = maxG;
        mB = maxB;
        int[][][] rSize = new int[ mR][ mG][ mB];
        for (int r = 0; r < mR; ++r) {
            for (int g = 0; g < mG; ++g) {
                for (int b = 0; b < mB; ++b) {
                    rSize[r][g][b] = calcSize(r - d2, g - d2, b - d2, r + d2, g + d2, b + d2);
                    if (d1 > 0) {
                        rSize[r][g][b] -= calcSize(r - d1 + 1, g - d1 + 1, b - d1 + 1, r + d1 - 1,
                                g + d1 - 1, b + d1 - 1);
                    }
                }
            }
        }
        double[][][] dp = new double[ mR][ mG][ mB];
        dp[startR][startG][startB] = 1.0;
        double[][][] sum = new double[ mR + 1][ mG + 1][ mB + 1];
        for (int i = 0; i < N - 1; ++i) {
            double[][][] next = new double[ mR][ mG][ mB];
            for (int r = 0; r < mR; ++r) {
                for (int g = 0; g < mG; ++g) {
                    for (int b = 0; b < mB; ++b) {
                        sum[r + 1][g + 1][b + 1] = sum[r][g + 1][b + 1] + sum[r + 1][g][b + 1]
                                + sum[r + 1][g + 1][b] - sum[r][g][b + 1] - sum[r + 1][g][b]
                                - sum[r][g + 1][b] + sum[r][g][b];
                        if (dp[r][g][b] > 0.0 && rSize[r][g][b] > 0) {
                            sum[r + 1][g + 1][b + 1] += dp[r][g][b] / rSize[r][g][b];
                        }
                    }
                }
            }
            for (int r = 0; r < mR; ++r) {
                for (int g = 0; g < mG; ++g) {
                    for (int b = 0; b < mB; ++b) {
                        next[r][g][b] = calcPropSum(r - d2, g - d2, b - d2, r + d2, g + d2, b + d2,
                                sum);
                        if (d1 > 0) {
                            next[r][g][b] -= calcPropSum(r - d1 + 1, g - d1 + 1, b - d1 + 1, r + d1
                                    - 1, g + d1 - 1, b + d1 - 1, sum);
                        }
                    }
                }
            }
            dp = next;
        }
        double res = 0;
        for (int r = 0; r < mR; ++r) {
            for (int g = 0; g < mG; ++g) {
                for (int b = 0; b < mB; ++b) {
                    if (Math.abs(r - startR) <= d2 && Math. abs(g - startG) <= d2
                            && Math. abs(b - startB) <= d2) {
                        if (Math.abs(r - startR) >= d1 || Math. abs(g - startG) >= d1
                                || Math. abs(b - startB) >= d1) {
                            res += dp[r][g][b];
                        }
                    }
                }
            }
        }
        return 1 - res;
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值