1 条题解

  • 0
    @ 2025-10-16 15:41:36

    题解说明

    问题背景:
    给定一个字符地图 mpmp(大小 n×mn \times m,字符取值大致为 'O''.'、其他),对地图上的每个非 '.' 的格子,以其为中心取 9×99 \times 9 的局部窗口,依据预先枚举出的“有效状态集”进行匹配与计数,并把该窗口对整体答案的贡献累加。最终以一个分数形式输出结果。

    核心建模:
    1)1) 9×99 \times 9 状态网格与不确定位:

    • 在一个 9×99 \times 9 网格 aa 中,格子取值为 {0,1,2}\{0,1,2\},其中 22 表示“未确定”。固定中心 (5,5)(5,5)11
    • aa 中的所有 22,通过递归枚举为 0011,得到若干“有效状态”。每个有效状态记录:
      • val[x]val[x]:该状态的“价值”(由函数 mx()/mn()mx()/mn() 计算得到且两者相等时有效)。
      • v[x][0],v[x][1],v[x][2]v[x][0], v[x][1], v[x][2]:该状态中值为 0/1/20/1/2 的位置集合(用位掩码表达)。
    • 有效性的判定来自 mx()mx()mn()mn() 的一致性(差值 d=mx()mn()d = mx()-mn()00),即对所有未确定位按两种棋盘色方案(奇偶)替换后,中心格 (5,5)(5,5) 的最终值一致。

    2)2) mx()/mn()mx()/mn() 与迭代函数 f()f()

    • mn()/mx()mn()/mx():将 aa 中的 22 分别替换为两套棋盘色((i+j)&1(i+j)\&1(i+j+1)&1(i+j+1)\&1),得到 bb
    • f()f():在 bb 上进行迭代“削减”,规则为:
      • 初始将 b[i][j]b[i][j] 乘以 2424(整数化)。
      • 每轮统计所有非 00 元素的相邻非 00 计数 d[i][j]d[i][j](四邻)。
      • 找到最小的 b[i][j]/d[i][j]\lfloor b[i][j]/d[i][j] \rfloor,令所有非 00b[i][j]b[i][j] 减去 d[i][j]mnd[i][j] \cdot mn
      • 直到无法继续(无 d>0d>0 或无可削减),返回中心 b[5][5]b[5][5]
    • 若两种棋盘替换下 f()f() 的结果相同,则该状态有效,valval 为此结果。

    3)3) 位掩码编码:

    • 9×99 \times 9 的每个位置分配一个 __int128\_\_int128 的唯一位(id[i][j]id[i][j]),从 22 的幂次递增(cur=2cur \mathrel{*}=2)。
    • v[x][k]v[x][k] 用来表达状态 xx 的集合位置(k=0,1,2k=0,1,2),便于在窗口匹配时快速与集合运算(与、或、交)。

    窗口计算:

    • 对地图每个非 '.' 的中心 (i,j)(i,j),构造 9×99 \times 9 窗口 ss
      • 超界补 00;字符映射 tn(O)=1, tn(.)=0, 其他=2tn('O')=1,\ tn('.')=0,\ \text{其他}=2
    • 提取 ss 的位掩码 k[0],k[1],k[2]k[0], k[1], k[2](窗口中 0/1/20/1/2 的位置集合)。
    • adad 计数:窗口中非 22 的数量补偿(代码中设为 82#282 - \#2,用于幂次偏移)。
    • 对每个有效状态 xx,筛除冲突:
      • 若窗口的 00 与状态的 11 有交,或窗口的 11 与状态的 00 有交,则不匹配。
    • 否则贡献:
      • 未确定位数量 =状态 2  窗口 2+ad= |\,状态\ 2 \ \cap\ 窗口\ 2\,| + ad(按位集合交的 popcount\text{popcount} 计算)。
      • 贡献 =2(未确定位数量)val[x]= 2^{(\text{未确定位数量})} \cdot val[x]
    • 把窗口贡献累加到总和 ansans__int128\_\_int128)。

    答案与约分:

    • 分母 d=(241)224=28224d = (2^{41})^2 \cdot 24 = 2^{82} \cdot 24
    • 用欧几里得算法求 gcd(ans,d)\gcd(ans, d),输出约分后的 ans/dans/d(若 ans=0ans=0 输出 0/10/1)。

    关键细节:

    • 中心 (5,5)(5,5) 预先固定为 11 后进行 DFS 枚举状态,显著减少搜索规模。
    • 分支选择策略:按距中心的曼哈顿距离优先,且仅在能降低 mx()mn()mx()-mn() 的点上分支(剪枝)。
    • f()f() 的“流式削减”过程等价于一个局部守恒的迭代削减,四邻均匀扣减,直到稳定。
    • 位掩码分成高低 6464 位来分别 popcount\text{popcount},避免超出 6464 位范围。
    • 所有计数采用 __int128\_\_int128 以防溢出;最终输出转 llll(约分后)。

    复杂度与可行性:

    • 状态枚举由剪枝与中心固定约束,数量 tottot 可控(代码容量预留到 85008500)。
    • 对每个窗口匹配 tottot 次,n×mn \times m 遍历;若地图稀疏(大量 '.' 跳过),总开销进一步降低。
    • 位运算与 popcount\text{popcount} 使匹配计算接近 O(1)O(1)

    总结:
    该程序先离线枚举 9×99 \times 9 的有效状态集及其价值,然后在整张地图上按 9×99 \times 9 滑窗进行集合匹配与计数,最终以一个分数形式给出全局贡献。设计的核心是:棋盘填充的双界(mx/mnmx/mn)一致性判定、迭代削减函数 f()f() 的整数化、位掩码的快速集合运算与幂次贡献,以及对总分数的精确约分输出。

    #include <bits/stdc++.h>
    #define ci const int          // 宏定义:ci作为const int的简写,用于定义常量整数
    #define ll long long          // 宏定义:ll作为long long的简写,用于定义长整型
    using namespace std;
    ci fx[4] = {1, -1, 0, 0}, fy[4] = {0, 0, 1, -1};  // 方向数组:下、上、右、左四个方向的坐标偏移
    int a[11][11], A[11][11], b[11][11], d[11][11], t[11][11][2], val[8500], tot, n, m, s[10][10];
    // a:9x9网格(1-based),存储当前状态;A:a的备份;b:辅助计算网格;d:邻接计数;t:暂未使用;
    // val:存储每个有效状态的价值;tot:有效状态总数;n,m:输入地图的行列数;s:当前9x9窗口的状态
    __int128 v[8500][3], id[11][11], pw[100], ans;
    // v:存储每个有效状态的0/1/2三类位置的位掩码;id:每个网格位置的唯一位标识;
    // pw:2的幂次表;ans:最终答案(分数分子)
    char mp[205][205];  // 存储输入的地图数据(205x205足够覆盖题目范围)
    
    // 结构体node:用于存储网格位置及优先级(曼哈顿距离),用于排序选择分支点
    struct node {
        int x, y, d;  // x,y:坐标;d:到中心(5,5)的曼哈顿距离(优先级)
        // 重载小于运算符:按d升序排序(距离中心近的优先)
        inline bool operator < (const node &t)const {
            return d < t.d;
        }
    };
    
    // 函数f:核心计算函数,用于mn()和mx()中,基于某种规则迭代计算网格值,最终返回中心(5,5)的值
    int f() {
        // 初始化b数组:每个元素乘以24(可能是为了后续整数运算避免浮点数)
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j)
                b[i][j] *= 24;
    
        // 迭代计算,直到所有非零元素的邻接计数为0或无法继续减小
        while (1) {
            int mn = 100;  // 记录最小的“b[i][j]/d[i][j]”值,初始设为较大值
    
            // 遍历网格,计算每个非零元素的邻接计数d[i][j](周围4个方向非零元素的数量)
            for (int i = 1; i <= 9; ++i)
                for (int j = 1; j <= 9; ++j)
                    if (b[i][j]) {  // 仅处理b[i][j]非零的位置
                        d[i][j] = 0;
                        for (int k = 0; k < 4; ++k)  // 检查四个方向
                            if (b[i + fx[k]][j + fy[k]])  // 相邻位置非零则计数+1
                                ++d[i][j];
                        // 若邻接计数非零,更新mn为当前最小值
                        if (d[i][j])
                            mn = min(mn, b[i][j] / d[i][j]);
                    }
    
            // 若mn未更新(无满足条件的元素),退出循环
            if (mn == 100)
                break;
    
            // 按mn值更新所有非零元素的b[i][j]
            for (int i = 1; i <= 9; ++i)
                for (int j = 1; j <= 9; ++j)
                    if (b[i][j])
                        b[i][j] -= d[i][j] * mn;
        }
    
        return b[5][5];  // 返回中心位置的最终值(用于后续mn/mx计算)
    }
    
    // 函数mn:计算当前a网格的“最小值”,针对a中值为2(未确定)的位置按(i+j)奇偶性赋值,调用f()返回结果
    int mn() {
        // 初始化b数组:a[i][j]为2时,b[i][j] = (i+j)的奇偶性(0或1);否则直接等于a[i][j]
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j)
                if (a[i][j] == 2)
                    b[i][j] = (i + j) & 1;
                else
                    b[i][j] = a[i][j];
    
        return f();  // 调用f()计算并返回结果
    }
    
    // 函数mx:计算当前a网格的“最大值”,针对a中值为2(未确定)的位置按(i+j+1)奇偶性赋值,调用f()返回结果
    int mx() {
        // 初始化b数组:a[i][j]为2时,b[i][j] = (i+j+1)的奇偶性(0或1);否则直接等于a[i][j]
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j)
                if (a[i][j] == 2)
                    b[i][j] = (i + j + 1) & 1;
                else
                    b[i][j] = a[i][j];
    
        return f();  // 调用f()计算并返回结果
    }
    
    // 函数dfs:深度优先搜索,枚举a网格中所有值为2(未确定)的位置的可能取值(0或1),记录有效状态
    void dfs() {
        int d = mx() - mn();  // 计算当前状态的“差值”(最大值-最小值)
    
        // 若差值为0,说明当前状态的最大值=最小值,是有效状态,记录该状态
        if (d == 0) {
            val[++tot] = mx();  // 记录该状态的价值(mx()或mn()均可,因两者相等)
    
            // 若价值为0,该状态无效,回退tot并返回
            if (!val[tot]) {
                --tot;
                return;
            }
    
            // 记录该状态下0/1/2三类位置的位掩码:v[tot][0]存0的位置,v[tot][1]存1的位置,v[tot][2]存2的位置
            for (int i = 1; i <= 9; ++i)
                for (int j = 1; j <= 9; ++j)
                    v[tot][a[i][j]] |= id[i][j];
    
            return;
        }
    
        // 差值不为0,需选择一个未确定位置(a[i][j]=2)分支搜索
        memcpy(A, a, sizeof(a));  // 备份当前a数组(后续分支搜索后恢复)
        vector<node>vec;          // 存储所有未确定位置的信息(坐标+到中心的曼哈顿距离)
    
        // 遍历网格,收集所有未确定位置(a[i][j]=2),计算其到中心(5,5)的曼哈顿距离
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j)
                if (a[i][j] == 2)
                    vec.push_back({i, j, abs(i - 5) + abs(j - 5)});
    
        sort(vec.begin(), vec.end());  // 按曼哈顿距离升序排序(优先选择靠近中心的位置分支)
        node nx = {0, 0, 0};           // 存储选中的分支位置
    
        // 遍历排序后的未确定位置,尝试将其设为0或1,找到能减小差值的位置作为分支点
        for (auto tmp : vec) {
            ci x = tmp.x, y = tmp.y;  // 当前尝试的位置(x,y)
            a[x][y] = 1;              // 先设为1
            // 若设为1后差值减小,选中该位置作为分支点,跳出循环
            if (mx() - mn() < d) {
                nx = tmp;
                break;
            }
    
            a[x][y] = 0;              // 再设为0
            // 若设为0后差值减小,选中该位置作为分支点,跳出循环
            if (mx() - mn() < d) {
                nx = tmp;
                break;
            }
        }
    
        memcpy(a, A, sizeof(a));  // 恢复a数组到分支前的状态
        // 分支搜索:分别将选中位置设为0、1,搜索后恢复为2(不影响后续其他分支)
        a[nx.x][nx.y] = 0, dfs();  // 设为0,递归搜索
        a[nx.x][nx.y] = 1, dfs();  // 设为1,递归搜索
        a[nx.x][nx.y] = 2;         // 恢复为2(未确定状态)
    }
    
    // 函数calc:计算当前9x9窗口(s数组)对应的贡献,累加到ans中
    void calc() {
        __int128 sum = 0, k[3] = {0, 0, 0};  // sum:当前窗口的贡献;k:窗口中0/1/2三类位置的位掩码
        int ad = 82;  // 统计窗口中未确定位置(2)的数量相关变量,初始82(9x9=81?可能是题目特定计算逻辑)
    
        // 遍历窗口s,构建k的位掩码,并计算ad(ad = 82 - 窗口中2的数量)
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j) {
                k[s[i][j]] |= id[i][j];  // 记录s中0/1/2的位置到k对应下标
                ad -= s[i][j] == 2;      // 若当前位置是2,ad减1
            }
    
        // 遍历所有有效状态(tot个),计算符合条件的状态对当前窗口的贡献
        for (int x = 1; x <= tot; ++x) {
            // 过滤条件:当前状态的1的位置不能与窗口的0的位置重叠,且当前状态的0的位置不能与窗口的1的位置重叠
            if ((k[0]&v[x][1]) || (k[1]&v[x][0]))
                continue;
    
            // 计算当前状态的贡献:2的幂次(未确定位置数量) * 状态价值val[x]
            // 未确定位置数量 = 状态中2的位置与窗口中2的位置的交集大小 + ad
            sum += pw[__builtin_popcountll((v[x][2] & k[2]) >> 60) + __builtin_popcountll((v[x][2] & k[2]) & ((
                          1ull << 60) - 1)) + ad] * val[x];
        }
    
        ans += sum;  // 将当前窗口的贡献累加到总答案ans中
    }
    
    // 函数tn:将地图字符转换为对应的数值(O→1,.→0,其他→2)
    int tn(char c) {
        if (c == 'O')
            return 1;
        else if (c == '.')
            return 0;
        else
            return 2;
    }
    
    // 函数G:计算两个__int128类型的最大公约数(GCD),递归实现欧几里得算法
    __int128 G(__int128 x, __int128 y) {
        return y ? G(y, x % y) : x;
    }
    
    int main() {
        __int128 cur = 1;  // 用于初始化id数组(每个位置的唯一位标识)
    
        // 初始化a数组(全部设为2,即未确定状态)和id数组(每个位置分配唯一的位,cur每次乘2)
        for (int i = 1; i <= 9; ++i)
            for (int j = 1; j <= 9; ++j)
                a[i][j] = 2, id[i][j] = cur, cur *= 2;
    
        // 初始化pw数组(2的幂次表,pw[i] = 2^i)
        pw[0] = 1;
        for (int i = 1; i < 100; ++i)
            pw[i] = pw[i - 1] * 2;
    
        // 设置中心(5,5)为1(确定状态),开始DFS枚举所有有效状态
        a[5][5] = 1, dfs();
    
        // 读取输入地图的行列数n和m,以及地图数据mp
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i)
            scanf("%s", mp[i] + 1);
    
        // 遍历地图的每个位置,以该位置为中心构建9x9窗口(s数组),调用calc()计算贡献
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                if (mp[i][j] == '.')  // 若当前位置是'.',可能跳过(题目特定逻辑)
                    continue;
    
                // 构建以(i,j)为中心的9x9窗口(x从-4到4,y从-4到4)
                for (int x = -4; x <= 4; ++x)
                    for (int y = -4; y <= 4; ++y) {
                        ci A = i + x, B = j + y;  // 窗口内的绝对坐标
                        // 若坐标在地图范围内,用mp[A][B]转换为数值;否则设为0
                        if (A >= 1 && A <= n && B >= 1 && B <= m)
                            s[x + 5][y + 5] = tn(mp[A][B]);  // 转换窗口坐标为1-based(x+5:-4→1,4→9)
                        else
                            s[x + 5][y + 5] = 0;
                    }
    
                calc();  // 计算当前窗口的贡献,累加到ans
            }
    
        // 计算最终答案的分母:d = (2^41)^2 * 24 = 2^82 * 24(题目特定的分母)
        __int128 d = 1ll << 41;
        d = d * d * 24;
        // 计算ans和d的最大公约数g,用于分数约分
        __int128 g = G(ans, d);
    
        // 输出结果:若ans为0,输出0/1;否则输出约分后的分数(分子/分母,转换为ll输出)
        if (!ans)
            printf("0/1");
        else
            printf("%lld/%lld", (ll)(ans / g), (ll)(d / g));
    }
    
    • 1