嘘~ 正在从服务器偷取页面 . . .

洛谷P4967 黑暗打击 题解


洛谷P4967 黑暗打击 题解

题目链接:P4967 黑暗打击

题意

有一群生物 ccj,他们在上次的星系中,发现了一群低等生物,于是想进行一波黑暗森林打击。这群低等生物即是 \(\mathsf{Hilbert}\) 鼹鼠,生活在 \(\mathsf{Hilbert}\) 星球,住在 \(\mathsf{Hilbert}\) 曲线土壤内。

这群生物决定用最傻的办法——灌水,来淹死他们。现在“高等”生物想知道,对于 \(n\) 阶的 \(\mathsf{Hilbert}\)​ 曲线,从上往下灌水,能淹没几个单位面积?

这是 \(1 \sim 4\) 阶的 \(\mathsf{Hilbert}\) 曲线:

\(h_1\),如最左图所示,是一个缺上口的正方形,这个正方形的边长为 \(1\)。 从\(h_2\) 开始,按照以下方法构造曲线 \(h_i\): 将 \(h_{i-1}\) 复制四份,按 \(2\times2\) 摆放。

把左上一份逆时针转 \(90^{\circ }\),右上一份顺时针转 \(90^{\circ }\),然后用三条单位线段将四分曲线按照左上-左下-右下-右上的顺序连接起来。如图所示,分别展示的是 \(h_2\)\(h_3\)\(h_4\)​。加粗的线段是额外用于连接的线段。

灌水方式:

(显然这个是 \(h_3\) 的灌水面积)绿色即为无法被灌到的地方,红色为可以灌到的地方,灰色为墙,所以答案是 \(26\),即为样例1。

一个方格有水当且仅当在它的上,左,右方格中有至少一个方格有水,最上面一层的空格都有水。

注,此题要求对 \(9223372036854775783\) 取模

输入格式

一个整数 \(n\),表示这个洞穴是 \(n\) 阶的 \(\mathsf{Hilbert}\) 曲线。

输出格式

一个整数 \(ans\),表示有 \(ans\) 个单位面积被淹没。

数据范围

\(n \le 10^{10000}\),数据均为手动构造,请注意常数。

这个模数是什么逆天模数啊(我还以为是 long long 自然溢出取模

总之不可否认的是,这是一个质数,而且是一个大的离谱的质数,因此记得开个**__int128**。

首先观察这个 Hillbert 曲线第 \(n\) 阶和第 \(n-1\)​ 阶的联系(直接搬图了)

图中黄色表示人为的含水划分线,橙色表示没有水,蓝色表示含水。

\(n=3\) 的情况如下

\(n=4\) 的情况如下

很明显,如果把 \(n=4\) 的情况从中间拆分成四个部分,那下面的两个部分就是 \(n=3\) 的情况

而上面的两个部分更像是若干个 \(n=2\)​​ 的情况拼接在一起。于是我们考虑以下划分方式

我们把 \(a_3,b_3,c_3\) 放到 \(n=4\) 的情况里

于是我们可以得到 \(a_4,b_4,c_4\)​ 的递推式 \[ \begin{aligned} &a_4=2 a_3+b_3+c_3 \\[6pt]&b_4=2 a_3+2 b_3+3 c_3+1 \\[6pt]&c_4=2 c_3+1 \end{aligned} \] 进而得到 \[ \begin{aligned} &a_n=2 a_{n-1}+b_{n-1}+c_{n-1} \\[6pt]&b_n=2 a_{n-1}+2 b_{n-1}+3 c_{n-1}+1 \\[6pt]&c_n=2 c_{n-1}+1 \\[6pt]&S_n=2 a_n+2 b_n+3 c_n+1 \end{aligned} \] 其中 \(S_n\) 表示含水格子的总面积。

看上去这道题接下来就要使用矩阵快速幂了 \[ \left[\begin{array}{llll} a_2 & b_2 & c_2 & 1 \end{array}\right] \times\left[\begin{array}{llll} 2 & 2 & 0 & 0 \\ 1 & 2 & 0 & 0 \\ 1 & 3 & 2 & 0 \\ 0 & 1 & 1 & 1 \end{array}\right]^{n-2} \] 其中 \(a_2=0,~b_2=1,~c_2=1\) ,是 \(n=2\) 时的情况。

然后我们看到了逆天的数据范围:\(n \leq 10^{10000}\) ,看来需要进一步优化

首先这个 \(c_n\) 实际上没什么必要,可以直接去掉变成 \(2^{n-1} - 1\) ,得到 \[ \begin{aligned} &a_n=2 a_{n-1}+b_{n-1}+2^{n-2} - 1 \\[6pt]&b_n=2 a_{n-1}+2 b_{n-1}+2^{n-1} - 1 + 2^{n-2} + 1 \\[6pt]&S_n=2 a_n+2 b_n+2^{n} - 1 + 2^{n-1} + 1 \end{aligned} \] 同时 \(b_n\) 实际上就是 \(S_{n-1}\) ,得到 \[ \begin{aligned} &a_n = 2a_{n-1} + S_{n-2} + 2^{n-2} - 1 \\[6pt]&S_n = 2a_n + 2S_{n-1} + 2^{n} - 1 + 2^{n-1} + 1 \end{aligned} \] 上面的改一下就是 \[ a_{n+1} = 2a_n + S_{n-1} + 2^{n-1} - 1 \] 于是 \[ S_{n} - a_{n+1} = S_{n-1} + 2^n - 1 \] 变形一下 \[ S_{n-1} - S_{n-2} - 2^{n-1} + 1 = a_{n} \] 再代回去 \[ S_n = (2S_{n-1} - 2S_{n-2} - 2^n + 2) + 2S_{n-1} + 2^n - 1 + 2^{n-1} + 1 \] 整理得(这一步有点难想到) \[ S_n + 2^n = 4(S_{n-1}+2^{n-1}) -2(S_{n-2} + 2^{n-2}) \]\(f_n = S_n + 2^n\) 则有 \[ f_n=4 f_{n-1}-2 f_{n-2} \] 接着实际上有两种解法

解法1

求一下通项公式 \[ f_n=\left(\frac{3}{2}-\sqrt{2}\right)(2-\sqrt{2})^{n-1}+\left(\frac{3}{2}+\sqrt{2}\right)(2+\sqrt{2})^{n-1} \] 得到 \[ S_n=\left(\frac{3}{2}-\sqrt{2}\right)(2-\sqrt{2})^{n-1}+\left(\frac{3}{2}+\sqrt{2}\right)(2+\sqrt{2})^{n-1}-2^n \] 于是这个问题只需要求出二次剩余就结束啦!(本题当然存在二次剩余

但是没写代码

解法2

听说可以证明 \[ f_n \equiv f_{n \bmod (p-1)} \quad \pmod p \] 我也不知道怎么证明(题解写的看不懂啊

但是这道题确实可以利用扩展欧拉定理优化矩阵快速幂(一般情况下是不能用的哦!)

时间复杂度 \(\mathcal{O}(T^3 \log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int __int128_t
#define gc() getchar()
#define pc(a) putchar(a)
// #define INF 0x3f3f3f3f3f3f3f3f
typedef vector< vector<int> > mat;
const int mod = 9223372036854775783ll, phi = mod - 1;
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
void add(int &x, int y) { (x += y) >= mod ? x -= mod : 0; }
#define N ((int)())

mat mul(mat &A, mat &B)
{
    int n = A.size(), m = A[0].size(), p = B[0].size();
    mat C(n, vector<int>(p));
    for(int k = 0; k < m; k++)
        for(int i = 0; i < n; i++)
            for(int j = 0; j < p; j++) add(C[i][j], A[i][k] * B[k][j] % mod);
    return C;
}
mat qpow(mat A, int k)
{
    int n = max(A.size(), A[0].size());
    mat B(n, vector<int>(n));
    for(int i = 0; i < n; i++) B[i][i] = 1;
    while(k)
    {
        if(k & 1) B = mul(B, A);
        k >>= 1; A = mul(A, A); 
    }
    return B;
}
int read()
{
    char ch = gc(); int x = 0; bool ck = false;
    for(; isdigit(ch); ch = gc())
    { 
        x = x * 10 + (ch ^ 48);
        if(x >= phi) { x %= phi; ck = true;  } 
    }
    if(ck) x += phi;
    return x;
}
void print(int x)
{
    if(x < 0) { pc('-'); x = -x; }
    if(x > 9) print(x / 10);
    pc(x % 10 + '0');
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n; n = read();
    mat A(1, vector<int>(4)), B(4, vector<int>(4)); 
    A[0][0] = 0; A[0][1] = 1; A[0][2] = 1; A[0][3] = 1;
    B[0][0] = 2; B[0][1] = 2; B[0][2] = 0; B[0][3] = 0;
    B[1][0] = 1; B[1][1] = 2; B[1][2] = 0; B[1][3] = 0;
    B[2][0] = 1; B[2][1] = 3; B[2][2] = 2; B[2][3] = 0;
    B[3][0] = 0; B[3][1] = 1; B[3][2] = 1; B[3][3] = 1;
    B = qpow(B, n - 2); A = mul(A, B);
    print((A[0][0] * 2 + A[0][1] * 2 + A[0][2] * 3 + 1) % mod);
    return 0;
}

不过关于什么情况下可以用扩展欧拉定理,我暂时也不清楚,所以贴几个图吧。讨论链接


参考文献

[1] https://www.luogu.com.cn/article/0d0183ip

[2] https://www.luogu.com.cn/article/ssb33lrf


题外话

好,真好。(内心复杂)(而且和这题没关系)

不过,(内心复杂),最近会更勤快刷题的啦!


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录