CF 707D Persistent Bookcase - 操作树、DFS
CF 707D Persistent Bookcase
题目链接:洛谷 CF 707D Persistent Bookcase CF 707D Persistent Bookcase
算法标签: 操作树
、深搜
题目描述:
维护一个二维零一矩阵(\(n,m \le 1000\)),支持四种操作(不超过 \(10^5\) 次):
-
将 \((i,j)\) 置一
-
将 \((i,j)\) 置零
-
将第 \(i\) 行零一反转
-
回到第 \(K\) 次操作前的状态
每次操作后输出全局一共有多少个一
题解:
操作树(其实写完我也不知道这是啥)
首先我们看看这四个操作:
- A 修改
- B 修改
- C 修改
- D 恢复
所以是不是可以看出来这道题毒瘤的是啥了嘛???? —— D操作啊!!!!!
这时候就需要操作树,大致就是按照操作来操作出一棵操作树,之后在操作树上操作各种操作,之后记录每次操作操作后的答案。 [滑稽.jpg] 【其实这么说我也看不懂】
额……额……额……额……额…… 好了好了说正事。
对于前三种情况,由于我们只是在这次进行修改,显然当前操作之后就是下一个操作,所以我们就在当前操作和下一操作之间连一条边。
对于第四种情况,我们需要得到第\(k\)次操作的状态,所以我们就需要在第\(k\)次操作时候直接得出当前操作的答案,也就是需要在当前操作和第\(k\)次操作之间连一条边,这样构成了一棵操作树(其实根本不是像上边那么晕)。
之后我们DFS这棵树,对每一个点进行该点的操作,之后直接走他的边,得出它的子节点的答案,回溯。最终我们可以得到所有点的答案。
由于时空问题(踏马乐 TMLE)开\(bitset\)这个好用且备受喜爱的东西来存状态。
P.S.如果还是不懂看代码注释鸭!!!
AC代码
#include <bits/stdc++.h>
using namespace std;
#define setI(x) freopen(x".in", "r", stdin);
#define setO(x) freopen(x".out", "w", stdout);
#define setIO(x) setI(x) setO(x)
typedef long long ll;
//quick_read
char *p1, *p2, buf[100000];
#define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ )
int rd() {
int x = 0, f = 1;
char c = nc();
while (c < 48) {
if (c == '-') {
f = -1;
}
c = nc();
}
while (c > 47) {
x = (((x << 2) + x) << 1) + (c ^ 48);
c = nc();
}
return x * f;
}
// quick_read over
const int N = 1010;
const int M = 1e5 + 10;
int n, m, q, tot;
int ans[M], opt[M], x[M], y[M];
bitset <N> all, mp[N];
// all 表示全1状态,用于取反
// mp[N] 存放状态
vector <int> edge[M];
// 存放树边
void dfs(int pos) {
bool flag = 0; // 这个点需不需要回溯
if (opt[pos] == 1 && mp[x[pos]][y[pos]] == 0) { // 操作A
flag = 1;
tot ++ ;
mp[x[pos]][y[pos]] = 1;
}
else if (opt[pos] == 2 && mp[x[pos]][y[pos]] == 1) { // 操作B
flag = 1;
tot -- ;
mp[x[pos]][y[pos]] = 0;
}
else if (opt[pos] == 3) { // 操作C
flag = 1;
tot -= mp[x[pos]].count(); // 减去当前1的个数
mp[x[pos]] ^= all; // 取反
tot += mp[x[pos]].count(); // 加上取反后1的个数
}
ans[pos] = tot; // 记录答案
for (int i = 0; i < (int)edge[pos].size(); i ++ ) {
dfs(edge[pos][i]); // 爬子节点
}
if (flag) { // 回溯
if (opt[pos] == 1) {
tot -- ;
mp[x[pos]][y[pos]] = 0;
}
else if (opt[pos] == 2) {
tot ++ ;
mp[x[pos]][y[pos]] = 1;
}
else if (opt[pos] == 3) {
tot -= mp[x[pos]].count();
mp[x[pos]] ^= all;
tot += mp[x[pos]].count();
}
}
}
int main() {
// setIO("now");
n = rd(), m = rd(), q = rd();
for (int i = 1; i <= m; i ++ ) {
all.set(i);
// 现学bitset,表示把i+1位置成1
}
memset(ans, -1, sizeof ans);
for (int i = 1; i <= q; i ++ ) {
opt[i] = rd(); // 操作种类
x[i] = rd(); // 操作第一关键字
if (opt[i] == 4) {
edge[x[i]].push_back(i); // 恢复 建边
}
else {
if (opt[i] < 3) {
y[i] = rd(); // 操作第二关键字
}
}
if (opt[i] < 4) {
edge[i - 1].push_back(i); // 修改 建边
}
}
dfs(0); // 爬树
for (int i = 1; i <= q; i ++ ) {
printf("%d\n", ans[i]);
}
return 0;
}