为了大家能更好理解Count the Cows的解法,我特地写了详细解题分析。
此问题使用到计算机科学非常重要思想:分治法
(Divide-and-Conquer
),就是分割问题、各个击破。将一个大问题,分割成许多小问题。如果小问题还是很难,就继续分割成更小的问题,直到问题变得容易解决。分割出来的小问题,称作子问题(subproblem
)。解决一个问题,等价于解决所有子问题,解决子问题可以解决更大问题。
题目来源
USACO 2021 February Contest, Gold Problem 3. Count the Cows
思路
根据题目生成数据,发现在$3^i$长度的正方形内,称为$i$-维正方形A,奶牛位于正对角线和反对角线上的格子,如下红色和黄色部分。蓝色格子代表有奶牛,白色代表无奶牛。
$i$-维正方形作为整体一个正方形,又拓展为$i+1$维正方形B。B的对角线和反对角线分别是A。每个$i$-维正方形的对角线上的小正方形是$i-1$-维正方形。这样可以无限拓展下去。例如:
1维正方形
$ {3\times3 \rightarrow 1\times1} \Rightarrow {3^1\times3^1 \rightarrow 3^0\times3^0}$
2维正方形
$9\times9 \rightarrow 3\times3 \Rightarrow 3^2\times3^2 \rightarrow 3^1\times3^1$
3维正方形
$27\times27 \rightarrow 9\times9 \Rightarrow 3^3\times3^3 \rightarrow 3^2\times3^2$
$\Rightarrow 3^i \times 3^i \Rightarrow 3^{i-1} \times 3^{i-1}$
基础正方形 $3\times3 $
以下是以$(x,y)$为终点,在基础正方形$3\times3 $内,对角线经过蓝色格子的数量
对称性
对角线$(x,y,d)$表示线段$(x,y) \rightarrow (x+d,y+d) $,进观察发现:对角线蓝色格子数:$(x,y,d)=(y,x,d)$,如下图:
因此集中考虑黄色的区域,另外一半的区域可以转化成黄色区域的对角线
可叠加
如下图,对角线经过蓝色格子数量: 绿色$=$橙色$+$红色,因此
$ans_{x,y,d}=F_{x+d,y+d} - F_{x-1,y-1}$
$F_{x,y}$ 代表线段$A(0,y-x) \rightarrow B(x,y)$ 经过蓝色格子的数量
此时问题转换成:任意点(x,y)往对角线走,到左上边缘,经过蓝色格子的数量,如下图:
划分区域
$i$-维正方形在坐标$(0,3^{i-1})$处形成的对角线,不经过任何蓝色格子,$ans=0$。
在此处分割,分上、下两个区域,如下图红色区域和黄色区域。
- 黄色区域会经过$1$、$1+3$、$1+3+5$号蓝色格子
- 红色区域会经过$4$号蓝色格子
进一步细化,进观察以下图,点(x,y)在不同区域形成对角线:
- 在0区不经过任何蓝格子,$ans=0$
- 在1区会经过1号蓝格子
- 在3区会经过1+3号蓝格子
- 在5区会经过1+3+5号蓝格子
- 在4区会经过4号蓝格子
分治
-
对角线每经过一个正方形,都可拆分成最小单位的基础正方形$3\times3$,最终答案是由正方形 $3\times3$累加而得。
如下图:
-
如下图,很明显
$ans=ans1+ans3+ans5$
正方形1,3都是完整的穿越的,因此
$ans=2*ans1+ans5$
算法拆分2部分,先计算穿越完整的正方形答案再计算不完整的正方形答案
算法设计
- 令 $diff=y-x$
- 对角线$(0,diff) \rightarrow (x,y)$在多大$3^i$的正方形?
1
2
3
4
5
6
7
8
9
10
|
//对角线(0,y-x)->(x,y)在多大3^i的正方形?
ill sqrLen(ill x, ill y)
{
ill max1 = max(x, y);
ill r = 1ll;
while (r <= max1)
r *= 3ll;
return r;
}
|
- 以点$(0,diff)$为对角线,也就是说直线 $y=x+diff$ 在长度是$n(n=3^i,n>diff)$的正方形内经过多少个蓝色格子?$n$必须能包住对角线。
1
|
ill fullSqrBlueCnt(ill diff, ill n)
|
- 情况一: 如果$n=1$或$n=3$
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if(n==1ll)
{
return 1ll;
}
else if(n==3ll)
{
if(diff==0ll)
return 3ll;
else if(diff==1)
return 0ll;
else if(diff==2)
return 1ll;
}
|
- 情况二: $diff < \frac{n}{3}$,穿过$1,3,5 == 3 \times1$
1
2
3
4
5
|
n /=3ll;
if (diff < n) //情况二:x<y<n,穿过1,3,5
{
return 3ll * fullSqrBlueCnt(diff, n);
}
|
- 情况三: $diff = \frac{n}{3}$,穿过空白格子
1
2
3
4
5
6
|
n /=3ll;
...
else if(diff==n) //情况三:穿过空白格子
{
return 0ll;
}
|
- 情况四: $\frac{n}{3} < diff < 2\times n$,穿过4号格子。处理办法:等同于在1号格子对角线的上部。计算走几步到达4号正方形,计算x,y坐标再递归调用情况二。
1
2
3
4
5
6
7
8
9
10
11
|
n /=3ll;
...
else if (diff < 2*n) //情况四:穿过4号格子,等同于在1号格子对角线的上部。计算走几步到达4号正方形,计算x,y坐标再递归调用情况二。
{
ill x1 = (2ll * n - diff);
ill y1 = 0ll;
if (x1 > y1)
swap(y1, x1);
return fullSqrBlueCnt(y1 - x1, n);
}
|
- 情况五:等同情况一,穿过4号格子,处理办法:等同于在1号格子对角线或者对角线的下部
1
2
3
4
5
6
7
|
n /=3ll;
...
else //情况五:等同情况一,穿过4号格子,处理办法:等同于在1号格子对角线或者对角线的下部
{
return fullSqrBlueCnt(diff % n, n);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
//在长度是n的正方形内,(0,diff) 开始的对角线,在n长度的正方形经过了多少个蓝色格子
ill fullSqrBlueCnt(ill diff, ill n)
{
//情况一:
if(n==1ll)
{
return 1ll;
}
else if(n==3ll)
{
if(diff==0ll)
return 3ll;
else if(diff==1)
return 0ll;
else if(diff==2)
return 1ll;
}
n /=3ll;
if (diff < n) //情况二:x<y<n,穿过1,3,5
{
return 3ll * fullSqrBlueCnt(diff, n);
}
else if(diff==n) //情况三:穿过空白格子
{
return 0ll;
}
else if (diff < 2*n) //情况四:穿过4号格子
{
ill x1 = (2ll * n - diff);
ill y1 = 0ll;
if (x1 > y1)
swap(y1, x1);
return fullSqrBlueCnt(y1 - x1, n);
}
else //情况五:等同情况一,穿过4号格子,处理办法:等同于在1号格子对角线或者对角线的下部
{
return fullSqrBlueCnt(diff % n, n);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
//在长度是n的正方形内,线段(0,y-x)->(x,y)经过了多少个蓝色格子
ill sum(ill x, ill y, ill n)
{
if (x > y)
swap(x, y);
if (n == 1)
return 1ll;
else if (n == 3ll)
return baseSqr[x][y];
ill topLeftSquareLen = n / 3ll; //左上角正方形大小3^(i-1)
if (y < topLeftSquareLen) //图一
{
return sum(x, y, topLeftSquareLen);
}
else if (x < topLeftSquareLen)
{
if (y < x+ topLeftSquareLen) //图二
{
return fullSqrBlueCnt(y-x, topLeftSquareLen);
}
else if (y >= x + topLeftSquareLen && y < 2ll * topLeftSquareLen) //图三
{
return 0ll;
}
else //图五 ,y >= 2ll * topLeftSquareLen
{
return sum(x, y%topLeftSquareLen, topLeftSquareLen);
}
}
else if (y == x + topLeftSquareLen) //图四
{
return 0ll;
}
else if (x < 2ll*topLeftSquareLen)
{
if (y < 2ll * topLeftSquareLen) //图六
{
return fullSqrBlueCnt(y-x, topLeftSquareLen) + sum(x%topLeftSquareLen, y%topLeftSquareLen, topLeftSquareLen);
}
else if (y < x + topLeftSquareLen) //图七
{
return 2ll*fullSqrBlueCnt(y-x, topLeftSquareLen);
}
else //图八
{
ill y1=2ll*topLeftSquareLen;
ill x1=x-(y-y1);
y1 %=topLeftSquareLen;
x1 %=topLeftSquareLen;
if(x1>y1)
swap(x1,y1);
return fullSqrBlueCnt(y1-x1, topLeftSquareLen);
}
}
else //图九
{
return 2ll*fullSqrBlueCnt(y-x, topLeftSquareLen)+sum(x%topLeftSquareLen, y%topLeftSquareLen, topLeftSquareLen);
}
}
|
以下各种情况对应代码$if$判断情况