某岛

… : "…アッカリ~ン . .. . " .. .
April 7, 2012

SRM 538

Brief description:

Level 3 又是一个关于堆砖的视图问题,感觉这类问题嘛、算法难度虽不高。。。
不过分析起来实非轻易,若不进行及时的总结,大概下次还是会落入 “重与漏” 的陷阱里。。。

今回题目是个二维版本,砖块分为 1×1 的彩色 cubes 和 1×2 的黑色 prisms,
。。|cubes| = n、 Sigma{cubes[..]} = s、黑砖数目为 B、宽度为 W 。。不要求摆放完。。

DIV 2 1050 SkewedPerspectives:
判定给定视图的合法性。(n ≤ 3、B ≤ 100、W ≤ 50 .. .)
DIV 1 1050 SkewedPerspective
对所有可能视图进行计数。(n ≤ 50、s ≤ 50、B ≤ 10、W ≤ 8 .. .)

 .. . Remember from the division 2 explanation that whenever we move to a new column to place a prism, 
it creates empty space that must be filled. 
The empty space will be composed of two parts: 

fill1: This is the number of places that forcefully need cubes 
(remember that this happens when the empty space in a column is odd). 
We need it to know the number of remaining cubes. 

fill2: The number of spaces of size 1x2. 
Which we can eventually replace with a prism or 2 cubes each. .. 

.. as low as possible .

两个问题一起看是比较科学的,要解决计数问题大概至少要理解判定版本的问题,解决判定问题是注意到上面的话。。
大概的含义如图所示,总是就是要尽量本着用料节省的原则。

.. .
const int MOD = 1000000007;
const int INF = 0x7fffffff;

const int N = 50;

int cubes[3], B, W;

bool check(string view){

    int h = SZ(view), b = B, w = W - 1, c[3]; CPY(c, cubes);
    int f0 = 0, f1 = 0;

    if (view[0] == 'b' && (SZ(view) == 1 || view[1] != 'b')) return false;

    REP(i, h) if (view[i] == 'b'){
        int _i = i++;
        while (i < SZ(view) && view[i] == 'b') ++i;
        int len = (i--) - _i;
        b -= (len + 1) / 2;
        if (len&1){
            --w, f0 += abs(_i - 1);
            if ((_i-1)&1) ++f1;
        }
    }
    else{
        --c[view[i]-'0'];
    }

    if (c[0] < 0 || c[1] < 0 || c[2] < 0 || b < 0 || w < 0) return false;
    int s = c[0] + c[1] + c[2];
    if (s < f1 || s + b*2 < f0) return false;
    return true;
}


class SkewedPerspectives {
public:
	vector <string> areTheyPossible(vector <int> _cubes, int _B, int _W, vector <string> views) {

	    B = _B, W = _W; cubes[0] = _cubes[0], cubes[1] = _cubes[1], cubes[2] = _cubes[2];

		vector<string> res; REP(i, SZ(views)) res.PB(check(views[i]) ? "valid" : "invalid");

		return res;
	}
};

对于判定版本的问题,将 Cubes 当做整体来处理还可做可不做的话,但是计数版本就不能退让了。
先用动态规划和二项式系数做出辅助数组 G。

G[i]: 从所有 Cubes 中,选出 i 个排成一行,颜色不同的方法数。

。。最大的难点是抽象出接下来要推的状态。。。

首先已经定义使用的 Cubes 数为 nn、
出现的单格的黑砖数为 b1(仅在此情况下需要切行)、
双格黑砖数为 b2 。。

接下来考虑填坑的问题。。。

f0: 需要填的坑的总数。 
f1: 其中必须用 cubes 填的坑。
f2: 其中可以用 prisms 填的坑。

。。知二求三,节省起见就存 f1、f2(.. .f1 < W .. . 2f2 ≤ 2B + n .. .)。。。 此外同判定问题一样这里仍然需要考虑摆放 b1 时的限制问题、需要加一维状态描述最后填的是否是黑砖。(bool bk)。。。 于是总的状态就是:

…
const int N = 50, BB = 10, WW = 8;
.. .
int F[N+1][WW][BB+1][WW][N/2+BB+1][2];
.. .
const int Null = -1;

int f(int nn = 0, int b1 = 0, int b2 = 0, int f1 = 0, int f2 = 0, bool bk = false){

    int &res = F[nn][b1][b2][f1][f2][bk];

    while (res == Null){
       .. .
    }

    return res;
}

最后状态转移转移分 nn+1、b1+1、b2+1 三种情况讨论即可。。。。

const int N = 50, BB = 10, WW = 8;

int B, W; VI cubes;

int F[N+1][WW][BB+1][WW][N/2+BB+1][2], G[N+1], C[N+1][N+1], tmp[N+1][N+1];
int n, s;

const int Null = -1;

int f(int nn = 0, int b1 = 0, int b2 = 0, int f1 = 0, int f2 = 0, bool bk = false){

    int &res = F[nn][b1][b2][f1][f2][bk];

    while (res == Null){

        res = 0; int rs = s-nn-f1, rb = B-b1-b2;
        if (rb < 0 || rs < 0 || b1 >= W || rs/2 + rb < f2) break;
        int h = nn + b1 + 2*b2;

        if (h) INC(res, G[nn]);

        if (rs) INC(res, f(nn+1, b1, b2, f1, f2, false));
        if (rb){
            INC(res, f(nn, b1, b2+1, f1, f2, true));
            if (h&&(!bk||h==2&&b2==1)) INC(res, f(nn, b1+1, b2, f1+((h-1)&1), f2+(h-1)/2, true));
        }
    }

    return res;
}

class SkewedPerspective{
public:
	int countThem(vector <int> _cubes, int _B, int _W){

	    B = _B, W = _W, cubes = _cubes, n = SZ(cubes), s = accumulate(ALL(cubes), 0);

	    RST(C); FOR_1(i, 0, s){C[i][0] = 1; REP_1(j, i) C[i][j] = sum(C[i-1][j-1], C[i-1][j]);}

	    RST(tmp); tmp[0][0] = 1; REP_1(i, n) FOR_1(j, 0, s) FOR_1_C(k, 0, min(cubes[i-1], j)) INC(tmp[i][j], pdt(C[j][k], tmp[i-1][j-k]));

	    CPY(G, tmp[n]); FLC(F, Null);

		return f();
	}
};

External link:

http://apps.topcoder.com/wiki/display/tc/SRM+538