题解:AtCoder AT_awc0088_d Control Panel Operation Sequence
【题目来源】
AtCoder:D - Control Panel Operation Sequence
【题目描述】
Takahashi performed \(N\) types of operations on a control panel with \(K\) lamps arranged in a row, executing one type per day over a total of \(N\) days.
Each operation was executed exactly once, but the order in which they were executed has been lost.
Each lamp is in one of two states: "on" or "off".
The state of the panel is represented by a 0/1 string of length \(K\), where the \(j\)-th character (\(1 \leq j \leq K\)) being 1 indicates that lamp \(j\) is on, and 0 indicates it is off.
The initial state of the panel is given by the string \(S\).
For operation \(i\) (\(1 \leq i \leq N\)), three 0/1 strings of length \(K\): \(A_i\), \(B_i\), \(X_i\) are defined.
Execution condition: Operation \(i\) can be executed only when the following conditions are all satisfied for every \(j\) (\(1 \leq j \leq K\)):
- If the \(j\)-th character of \(A_i\) is
1, then lamp \(j\) must be on. - If the \(j\)-th character of \(B_i\) is
1, then lamp \(j\) must be off.
When both the \(j\)-th character of \(A_i\) and the \(j\)-th character of \(B_i\) are 0, the state of lamp \(j\) does not matter.
On the other hand, it is possible that both the \(j\)-th character of \(A_i\) and the \(j\)-th character of \(B_i\) are 1. In that case, lamp \(j\) must be both on and off simultaneously, which cannot be satisfied by any panel state, so operation \(i\) can never be executed under any circumstances.
Effect: When operation \(i\) is executed, for each \(j\) (\(1 \leq j \leq K\)):
- If the \(j\)-th character of \(X_i\) is
1, the on/off state of lamp \(j\) is toggled. - If the \(j\)-th character of \(X_i\) is
0, lamp \(j\) does not change.
Aoki recorded only the number of lamps that are on after the operation on each day.
As the initial record, the number of lamps on after the operation on day \(t\) (\(1 \leq t \leq N\)), \(C_t\), is given.
This record is subject to \(Q\) corrections afterwards.
In the \(q\)-th correction (\(1 \leq q \leq Q\)), the value for day \(T_q\) in the current record is changed to \(Y_q\). All changes from previous corrections are retained, and the new correction is applied as an overwrite.
After each correction, find the number of operation execution orders that are consistent with the record at that point, modulo \(998244353\).
Here, an operation execution order is a permutation \((p_1, p_2, \ldots, p_N)\) of \((1, 2, \ldots, N)\), and a permutation is consistent with the record if and only if all of the following are satisfied:
- The initial state of the panel is \(S\).
- On day \(t\) (\(t = 1, 2, \ldots, N\)), operation \(p_t\) is executed.
- The panel state immediately before executing operation \(p_t\) satisfies the execution condition of operation \(p_t\).
- The number of lamps on after the operation on day \(t\) equals \(C_t\) in the record.
Note that different permutations of operation numbers are counted as different execution orders, even if they only swap operations with identical conditions and effects.
高桥对一个有 \(K\) 盏灯排成一行的控制面板进行了 \(N\) 种操作,每天执行一种操作,总共执行了 \(N\) 天。
每种操作恰好执行了一次,但执行的顺序已经丢失。
每盏灯处于两种状态之一:"开"或"关"。
面板的状态由一个长度为 \(K\) 的 0/1 字符串表示,其中第 \(j\) 个字符(\(1 \leq j \leq K\))为 1 表示灯 \(j\) 亮着,0 表示灯灭着。
面板的初始状态由字符串 \(S\) 给出。
对于操作 \(i\)(\(1 \leq i \leq N\)),定义了三个长度为 \(K\) 的 0/1 字符串:\(A_i\)、\(B_i\)、\(X_i\)。
执行条件: 仅当对于每个 \(j\)(\(1 \leq j \leq K\))都满足以下所有条件时,操作 \(i\) 才能被执行:
- 如果 \(A_i\) 的第 \(j\) 个字符是
1,则灯 \(j\) 必须亮着。 - 如果 \(B_i\) 的第 \(j\) 个字符是
1,则灯 \(j\) 必须灭着。
当 \(A_i\) 的第 \(j\) 个字符和 \(B_i\) 的第 \(j\) 个字符都是 0 时,灯 \(j\) 的状态无关紧要。
另一方面,有可能 \(A_i\) 的第 \(j\) 个字符和 \(B_i\) 的第 \(j\) 个字符都是 1。在这种情况下,灯 \(j\) 必须同时亮着和灭着,这在任何面板状态下都无法满足,因此操作 \(i\) 在任何情况下都无法执行。
效果: 当执行操作 \(i\) 时,对于每个 \(j\)(\(1 \leq j \leq K\)):
- 如果 \(X_i\) 的第 \(j\) 个字符是
1,则灯 \(j\) 的开/关状态被切换。 - 如果 \(X_i\) 的第 \(j\) 个字符是
0,则灯 \(j\) 不变。
青木只记录了每天操作后亮着的灯的数量。
作为初始记录,给出了第 \(t\) 天(\(1 \leq t \leq N\))操作后亮着的灯的数量 \(C_t\)。
此后,这份记录将经历 \(Q\) 次修正。
在第 \(q\) 次修正(\(1 \leq q \leq Q\))中,当前记录中第 \(T_q\) 天的值被更改为 \(Y_q\)。之前所有修正的更改都会保留,新的修正会覆盖写入。
每次修正后,求与该时刻记录一致的操作执行顺序的数量,对 \(998244353\) 取模。
这里,操作执行顺序是 \((1, 2, \ldots, N)\) 的一个排列 \((p_1, p_2, \ldots, p_N)\),一个排列与记录一致当且仅当满足以下所有条件:
- 面板的初始状态是 \(S\)。
- 在第 \(t\) 天(\(t = 1, 2, \ldots, N\)),执行操作 \(p_t\)。
- 执行操作 \(p_t\) 前的面板状态满足操作 \(p_t\) 的执行条件。
- 第 \(t\) 天操作后亮着的灯的数量等于记录中的 \(C_t\)。
注意,即使只是交换了条件和效果完全相同的操作,不同的操作编号排列也被视为不同的执行顺序。
【输入】
\(N\) \(K\) \(Q\)
\(S\)
\(C_1\) \(C_2\) \(\ldots\) \(C_N\)
\(A_1\) \(B_1\) \(X_1\)
\(A_2\) \(B_2\) \(X_2\)
\(\vdots\)
\(A_N\) \(B_N\) \(X_N\)
\(T_1\) \(Y_1\)
\(T_2\) \(Y_2\)
\(\vdots\)
\(T_Q\) \(Y_Q\)
- The first line contains the number of operation types \(N\), the number of lamps \(K\), and the number of corrections \(Q\), separated by spaces.
- The second line contains a
0/1string \(S\) of length \(K\) representing the initial state of the panel. - The third line contains the integers \(C_1, C_2, \ldots, C_N\) representing the initial record values, separated by spaces.
- The following \(N\) lines, where the \(i\)-th line contains the
0/1strings \(A_i\), \(B_i\), \(X_i\) of length \(K\) representing the execution condition and effect of operation \(i\), separated by spaces. - The following \(Q\) lines, where the \(q\)-th line contains integers \(T_q\), \(Y_q\) representing the \(q\)-th correction, separated by spaces. This means the value for day \(T_q\) in the current record is changed to \(Y_q\).
【输出】
Output \(Q\) lines.
On the \(q\)-th line, output the number of operation execution orders consistent with the record immediately after the \(q\)-th correction, modulo \(998244353\).
【输入样例】
3 3 4
010
2 1 2
010 000 100
100 000 001
000 001 010
2 3
2 1
1 0
1 2
【输出样例】
0
1
0
1
【核心思想】
-
问题分析:给定 \(N\) 种操作(每种执行一次)、\(K\) 盏灯的初始状态 \(S\),以及每天操作后亮灯数量的记录 \(C_t\)。每次修正后需要统计与记录一致的操作执行顺序数量。这是一个状态枚举 + 哈希表计数问题,关键在于 \(N \leq 9\) 的数据范围允许枚举全排列。
-
算法选择:
- 全排列枚举:利用 \(N \leq 9\) 的小数据范围,枚举所有 \(N!\) 种操作顺序
- 哈希表计数:使用
map<vector<int>, int>记录每种结果序列出现的次数 - 状态模拟:模拟每种排列下灯的状态变化,验证执行条件并记录结果
-
关键步骤:
- 预处理检查:检查是否存在 \(A_i[j] = B_i[j] = '1'\) 的矛盾操作,若存在则所有答案为 \(0\)
- 全排列枚举:使用
next_permutation枚举所有操作顺序 \((p_1, p_2, \ldots, p_N)\) - 状态模拟(对每个排列):
- 初始化灯状态 \(t = S\)
- 按顺序执行操作 \(p_{idx}\)(\(idx = 1\) 到 \(N\)):
- 条件检查:验证当前状态满足 \(A_i\)(必须为1的位置)和 \(B_i\)(必须为0的位置)
- 执行翻转:根据 \(X_i\) 翻转对应灯的状态
- 记录结果:统计当前亮灯数量,存入向量 \(v\)
- 若所有操作都满足条件,则
mp[v]++统计该结果序列
- 查询处理:对于每次修正 \((T_q, Y_q)\),更新 \(C_{T_q} = Y_q\),输出
mp[C]
-
时间/空间复杂度:
- 时间复杂度:\(O(N! \cdot N \cdot K + Q \cdot N)\),枚举 \(N!\) 种排列,每种模拟 \(N\) 个操作,每个操作检查/翻转 \(K\) 盏灯
- 空间复杂度:\(O(N! \cdot N)\),哈希表存储最多 \(N!\) 种不同的结果序列
-
哈希表与全排列枚举的核心思想:
- 离线预处理:先枚举所有可能的操作顺序,记录每种结果序列的出现次数,查询时 \(O(1)\) 回答
- 状态压缩思想:将每天的操作结果(亮灯数量序列)作为向量存储,利用
map实现快速查找 - 小数据范围利用:\(N \leq 9\) 时 \(N! = 362880\) 可接受,\(N > 10\) 时不可行
- 模拟验证:严格按照题目条件模拟状态变化,确保执行条件满足
- 适用于小数据范围、需要统计方案数、结果可哈希的问题
【算法标签】
哈希表
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义长整型别名,便于处理大数据
#define int long long
// 定义数组最大容量(较小,适用于小规模数据)
const int N = 15;
// 全局变量声明
int n, k, q; // n: 操作次数, k: 字符串长度, q: 查询次数
string s; // 初始状态字符串
int c[N]; // 存储查询结果
string a[N], b[N], x[N]; // a: 前置条件1, b: 前置条件2, x: 翻转操作
int p[N]; // 排列数组,用于枚举操作顺序
// 主函数入口
signed main()
{
// 读取基本参数
cin >> n >> k >> q;
cin >> s;
s = " " + s; // 将字符串索引改为从1开始
// 读取每个操作的查询结果
for (int i = 1; i <= n; i++)
cin >> c[i];
// 读取每个操作的三个字符串,并将索引改为从1开始
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i] >> x[i];
a[i] = " " + a[i];
b[i] = " " + b[i];
x[i] = " " + x[i];
}
// 检查是否存在矛盾:某个位置同时要求为1和0
for (int i = 1; i <= n; i++)
for (int j = 1; j <= k; j++)
if (a[i][j] == '1' && b[i][j] == '1')
{
// 存在矛盾,所有查询结果都为0
while (q--)
{
int t, y;
cin >> t >> y;
cout << 0 << endl;
}
return 0;
}
// 使用map记录每种操作序列结果的出现次数
map<vector<int>, int> mp;
// 初始化排列数组
for (int i = 1; i <= n; i++)
p[i] = i;
// 枚举所有可能的操作顺序(全排列)
do
{
string t = s; // 复制初始状态
vector<int> v; // 记录每次操作后的1的个数
bool ok = true; // 标记当前排列是否可行
// 按当前排列顺序执行操作
for (int idx = 1; idx <= n; idx++)
{
int i = p[idx]; // 获取当前要执行的操作编号
// 检查前置条件是否满足
for (int j = 1; j <= k; j++)
{
if (a[i][j] == '1' && t[j] == '0')
ok = false; // 需要为1但实际为0
if (b[i][j] == '1' && t[j] == '1')
ok = false; // 需要为0但实际为1
}
if (!ok) break; // 条件不满足,放弃此排列
// 执行翻转操作
for (int j = 1; j <= k; j++)
if (x[i][j] == '1')
t[j] = (t[j] == '0' ? '1' : '0');
// 统计当前字符串中1的个数
int cnt = 0;
for (int j = 1; j <= k; j++)
if (t[j] == '1')
cnt++;
v.push_back(cnt); // 记录结果
}
if (!ok) continue; // 跳过不可行的排列
mp[v]++; // 统计该结果序列出现的次数
} while (next_permutation(p + 1, p + n + 1)); // 生成下一个排列
// 处理查询
while (q--)
{
int t, y;
cin >> t >> y;
c[t] = y; // 更新查询结果
// 构建查询向量
vector<int> query;
for (int i = 1; i <= n; i++)
query.push_back(c[i]);
// 输出对应结果序列出现的次数
cout << mp[query] << endl;
}
return 0;
}
【运行结果】
3 3 4
010
2 1 2
010 000 100
100 000 001
000 001 010
2 3
0
2 1
1
1 0
0
1 2
1
浙公网安备 33010602011771号