跳转至

F. 雷方块

分数:200分

向着星辰...... 欢迎来到......

你向往常一样领取派遣探索和每日任务奖励,但今天凯瑟琳给了你一件特别的委托。

你看了一眼任务说明,只需要完成一个雷方块解密就能拿到1600原石,想着已经75发大保底了,做完就能看到奶奶在壶里脸红的样子,立刻按照指引传送到天云山上下顶端的锚点。 原本雷鸟所在的平台已经密密麻麻铺上了雷方块,你扔了发天动万象,毫无头绪。

任务

给定一个由 0,1,2,3 这四个数构成的矩阵,代表由三种状态的方块构成的阵列,其中 0 表示空缺,1,2,3 分别为三种状态。 敲击一个方块会使这个方块和相邻四个位置中的方块按照 (1, 2, 3) 的顺序轮回。 求解每个方块需要敲击多少次能让全部方块状态变成3。

注意

注意到一个方块连续敲三次整体状态还原。 题目保证在每个方块敲击次数不超过2的情况下具有唯一解。

输入

一个二进制文件 in.data ,文件内容为

  • 2 个 32 位整数,n1 和 n2,保证 n1 \leq n2
  • 一个 n2 行 n1 列的 32 位整数矩阵,按行优先方式存储,每个数表示这个位置是否有方块以及方块的初始状态。

样例输入

4, 4
0, 1, 1, 2
1, 3, 0, 3
0, 3, 1, 1
3, 1, 2, 2

二进制形态:

4 4 0 1 1 2 1 3 0 3 0 3 1 1 3 1 2 2

输出

一个二进制文件 out.data ,文件内容为一个 n2 行 n1 列的 32 位整数矩阵,按行优先方式存储,每个数表示这个位置的方块需要敲击的次数。

额外要求

为了保证输出唯一,额外提出以下两点要求。

  • 敲击次数必须大于等于 0,小于等于 2。
  • 如果一个位置没有方块,则矩阵中这个位置的整数必须为 0。

样例输出

0, 1, 1, 0
2, 0, 0, 0
0, 0, 0, 0
0, 0, 2, 2

附件

本题没有附件,如果需要测试数据可以使用以下代码生成测试数据,需要自行挑出有唯一解的情况

import numpy
n1=16
n2=16
x=numpy.zeros(2+n1*n2,dtype=numpy.int32)
x[0]=n1
x[1]=n2
r=numpy.random.rand(n1*n2)
x[2:][r>0.1]=1
x[2:][r>0.4]=2
x[2:][r>0.7]=3
x.tofile('in.data')

可以使用以下代码校验结果

import numpy
x=numpy.fromfile('in.data',dtype=numpy.int32)
n1=x[0]
n2=x[1]
m=x[2:].reshape((n2,n1))

m_=numpy.fromfile('out.data',dtype=numpy.int32).reshape((n2,n1))
assert((m_<=2).all()) # 敲3次等于没敲
assert((m_>=0).all()) # 怎么想都不能敲-1次
m__=numpy.array(m_)
m__[:,1:]+=m_[:,:-1]
m__[:,:-1]+=m_[:,1:]
m__[1:,:]+=m_[:-1,:]
m__[:-1,:]+=m_[1:,:]
assert(((m+m__)%3*m==0).all()) # 所有方块状态为3
assert(((m==0)*m_==0).all())   # 没有方块的位置必须是0

提示

  • 本题写成模3下的线性方程组后可以使用直接求解的方式,包括高斯消元和LU分解,大致过程如下:
import numpy
x=numpy.fromfile('in.data',dtype=numpy.int32)
n1=x[0]
n2=x[1]
m=x[2:].reshape((n2,n1))
im=numpy.zeros((n2,n1),dtype=numpy.int32)

ci=0
for i in range(n2):
    for j in range(n1):
        if m[i,j]:
            im[i,j]=ci
            ci+=1
        else:
            im[i,j]=-1
nl=((0,1),(0,-1),(1,0),(-1,0),(0,0))

import sage.all
t=sage.all.Integers(3)

a=sage.all.matrix(t,ci,ci)
y=sage.all.vector(t,ci)
for i in range(n2):
    for j in range(n1):
        ci=im[i,j]
        if ci>=0:
            y[ci]=3-m[i,j]
            for nl_ in nl:
                i_=i+nl_[0]
                j_=j+nl_[1]
                if 0<=i_ and i_<n2 and 0<=j_ and j_<n1:
                    ci_=im[i_,j_]
                    if ci_>=0:
                        a[ci_,ci]=1

x_t=a.solve_right(y)

x=numpy.zeros((n2,n1),dtype=numpy.int32)
for i in range(n2):
    for j in range(n1):
        ci=im[i,j]
        if ci>=0:
            x[i,j]=x_t[ci]
x.tofile('out.data')
  • 复杂度为 n1^3*n2 的朴素高斯消元算法确能拿到满分。

n方可能过百万,但n方过百万有点不可能

评分标准

提交一个 cpp 文件,程序使用 g++ -O3 -march=native -fopenmp 编译,在 2 个核心上运行,设置环境变量 OMP_NUM_THREADS=2,运行脚本:

g++ -O3 -march=native -fopenmp program.cpp -o program
time OMP_NUM_THREADS=2 ./program

共 3 个测试点,每个测试点得到正确结果获得基本分数,性能分数与运行时间倒数成正比。

final_score = correct ? base_score + perf_score * min(goal / time, 1) : 0

n1 n2 empty rate base score perf score limit goal
1 4 4 18.75% 10 0 1 s 1 s
2 512 512 9.96% 5 90 2 min 1.35 s
3 512 1024 9.96% 5 90 5 min 2.5 s

评测环境

容器镜像:gcc 。已安装 g++openmp