首页 > 技术文章 > CF 707D Persistent Bookcase - 操作树、DFS

littleseven777 2019-11-12 21:38 原文

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;

}

推荐阅读