1 条题解
-
0
题解说明
问题背景:
给定一个字符地图 (大小 ,字符取值大致为'O'
、'.'
、其他),对地图上的每个非'.'
的格子,以其为中心取 的局部窗口,依据预先枚举出的“有效状态集”进行匹配与计数,并把该窗口对整体答案的贡献累加。最终以一个分数形式输出结果。核心建模:
状态网格与不确定位:- 在一个 网格 中,格子取值为 ,其中 表示“未确定”。固定中心 为 。
- 对 中的所有 ,通过递归枚举为 或 ,得到若干“有效状态”。每个有效状态记录:
- :该状态的“价值”(由函数 计算得到且两者相等时有效)。
- :该状态中值为 的位置集合(用位掩码表达)。
- 有效性的判定来自 与 的一致性(差值 为 ),即对所有未确定位按两种棋盘色方案(奇偶)替换后,中心格 的最终值一致。
与迭代函数 :
- :将 中的 分别替换为两套棋盘色( 与 ),得到 。
- :在 上进行迭代“削减”,规则为:
- 初始将 乘以 (整数化)。
- 每轮统计所有非 元素的相邻非 计数 (四邻)。
- 找到最小的 ,令所有非 的 减去 。
- 直到无法继续(无 或无可削减),返回中心 。
- 若两种棋盘替换下 的结果相同,则该状态有效, 为此结果。
位掩码编码:
- 为 的每个位置分配一个 的唯一位(),从 的幂次递增()。
- 用来表达状态 的集合位置(),便于在窗口匹配时快速与集合运算(与、或、交)。
窗口计算:
- 对地图每个非
'.'
的中心 ,构造 窗口 :- 超界补 ;字符映射 。
- 提取 的位掩码 (窗口中 的位置集合)。
- 计数:窗口中非 的数量补偿(代码中设为 ,用于幂次偏移)。
- 对每个有效状态 ,筛除冲突:
- 若窗口的 与状态的 有交,或窗口的 与状态的 有交,则不匹配。
- 否则贡献:
- 未确定位数量 (按位集合交的 计算)。
- 贡献 。
- 把窗口贡献累加到总和 ()。
答案与约分:
- 分母 。
- 用欧几里得算法求 ,输出约分后的 (若 输出 )。
关键细节:
- 中心 预先固定为 后进行 DFS 枚举状态,显著减少搜索规模。
- 分支选择策略:按距中心的曼哈顿距离优先,且仅在能降低 的点上分支(剪枝)。
- 的“流式削减”过程等价于一个局部守恒的迭代削减,四邻均匀扣减,直到稳定。
- 位掩码分成高低 位来分别 ,避免超出 位范围。
- 所有计数采用 以防溢出;最终输出转 (约分后)。
复杂度与可行性:
- 状态枚举由剪枝与中心固定约束,数量 可控(代码容量预留到 )。
- 对每个窗口匹配 次, 遍历;若地图稀疏(大量
'.'
跳过),总开销进一步降低。 - 位运算与 使匹配计算接近 。
总结:
该程序先离线枚举 的有效状态集及其价值,然后在整张地图上按 滑窗进行集合匹配与计数,最终以一个分数形式给出全局贡献。设计的核心是:棋盘填充的双界()一致性判定、迭代削减函数 的整数化、位掩码的快速集合运算与幂次贡献,以及对总分数的精确约分输出。#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
信息
- ID
- 3187
- 时间
- 5000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者