Codejam之Alphabet Cake

问题描述

需要为party准备一个蛋糕,R行 C列的格子形状。助理已经把每个孩子的名字首字母写在了蛋糕的单元里,每个孩子的名字首字母都是唯一的,没有重复。每个单元至多有1个首字母。

切分蛋糕时,每个孩子的蛋糕都是矩形的,只包含自己的名字首字母,且不包含其他孩子的名字首字母。

输入:第一行的数字T,有T个测试用例。
每个测试用例开始是R和C,接着R行,每行有C个字符,表示开始的蛋糕状况。?号表示这个单元为空,英文字母表示助理在这个单元上写的孩子名字。

输出:蛋糕的切分情况,每个单元上的字母表示分给哪个孩子。每个孩子被分得的区域必须是矩形。
不能添加新的字母
如果有多个答案,输出其中任意一个都可以。

小数据集:1<=R<=12,1<=C<=12,R*C<=12
大数据集:1<=R<=25,1<=C<=25

Codejam之Alphabet Cake_第1张图片

问题解决

一开始的想法是遍历数组,如果cake i j 等于?,向右找第一个不等于?的字母,如果没有向上找,如果没有向左或下找。
这种方法得到的可能不是矩形。

??
?E
C?

得到

CC
EE
CE

遍历数组,如果cake i j不等于?,将它当作1by1的box,先上下拉伸,再左右拉伸,不能碰到其他字母。这种方法也可能失败。
如下面这个示例,结果有一个单元没有被填充。

A?B
C??
??D
?EF

得到

AAB
C?B
CDD
CEF

暴力法

小数据集中,一个蛋糕最多有12个单元,输入的蛋糕上有L个字母,已经占据了L个单元,那么还剩下12-L个单元。对每一个空白单元,有L个字母可以选择。

情况有L的(12-L)次方种,L在1到12之间,因此最大值是5的7次方=78125

对于每种情况进行检查,检查如下:
遍历数组,找到每个字母的最左上的位置和最右下的位置,出现次数。对每个字母,检查最左上位置和最右下位置构成的矩形是否只包含这个字母,且区域的大小等于出现次数。

分治法

如果一个蛋糕上只有一个字母,用这个字母填充完全。
如果一个蛋糕上有两个或两个以上字母,可以切分为两块更小的蛋糕。两块小蛋糕至少有一个字母。

为使两块小蛋糕至少有一个字母,遵循的切割规则:

1. 如果字母全部都在一列上,从中间切开。(这种情况再竖着切只会得到全是?的一块)

??A
???
??C

得到

??A

???
??C

2. 选择最左的字母所在列的右边切开

?A?
B??
??C

得到

?
B
?

A?
??
?C

int r = Integer.parseInt(aline.split(" ")[0]);
int c = Integer.parseInt(aline.split(" ")[1]);
char[][] cake = new char[r][c];
for(int i=0;i<r;i++) {
    aline=reader.readLine();
    for(int j=0;j {
        cake[i][j] = aline.charAt(j);
    }
}
divide_and_conquer(cake,0,0,r,c);
private static void divide_and_conquer(char[][] cake,int si,int sj,int rno,int cno) {
    //si和sj是矩形的左上点的索引,rno是行数(宽度),cno是列数(长度)
    //(si,sj)      ... (si,sj+cno-1)
    //(si+rno-1,sj)... (si+rno-1,sj+cno-1)
    if(rno==1 && cno==1) return;

    //如果只有一个字符,用这个字符填充区域
    int lno = 0;
    char l = '?';
    for(int j=sj;j<=sj+cno-1;j++) {
        for(int i=si;i<=si+rno-1;i++) {
            if(cake[i][j]!='?') {
                lno++;
                l=cake[i][j];
            }
        }
    }
    if(lno==1) {
        for(int i=si;i<=si+rno-1;i++) {
            for(int j=sj;j<=sj+cno-1;j++) {
                cake[i][j]=l;
            }
        }
        return;
    }


    //如果全部字母存在一列上,从上到下找到第一个字母,从下到上找到最后一个字母,从两者中间切割,为保证切成的两块至少含有一个字母
    int llno = 0;  //记录字母是否都在一列上
    int llrno = 0;  //如果都在一列上,记录是都在哪一列上
    for(int j=sj;j<=sj+cno-1;j++) {
        for(int i=si;i<=si+rno-1;i++) {
            if(cake[i][j]!='?') {
                llno++;
                llrno=j;
                break;
            }
        }
    }
    if(llno==1) {
        int topj = si;
        int bottomj = si+rno-1;
        while(cake[topj][llrno]=='?')
            topj++;
        while(cake[bottomj][llrno]=='?')
            bottomj--;
        int medium = topj+(bottomj-topj)/2;
        divide_and_conquer(cake,si,sj,medium-si+1,cno);
        divide_and_conquer(cake,medium+1,sj,si+rno-1-medium,cno);
        return;
    }

    //找到最左的字母存在的列,从右边分割 
    for(int j=sj;j<=sj+cno-1;j++) {
        for(int i=si;i<=si+rno-1;i++) {
            if(cake[i][j]!='?') {
                divide_and_conquer(cake,si,sj,rno,j-sj+1);
                divide_and_conquer(cake,si,j+1,rno,cno-(j-sj+1));
                return;
            }
        }
    }
}

你可能感兴趣的:(codejam)