zoukankan      html  css  js  c++  java
  • 操作树学习笔记

    前言

    这个数据结构(或许不是数据结构而仅仅是一种暴力?)比较冷门,网上也没找到什么博客。我尽量讲得详细。

    昨天做一道CF的题目的时候就遇到了"操作树"。

    昨天看到操作树,因为看的博客讲的没那么详细,而且比较抽象,一直没有想明白,然后晚上睡觉的时候YY出来应该怎么弄了。

    这东西蛮简单的,比较低级,但是有些时候却挺有用的。

    一般可以用操作树的题目:

    • 允许离线(这是最重要的)

    • 需要查询历史版本

    • 当前操作受到之前操作的影响。

    • 需要维护的状态不是那么复杂

    操作树该怎么建

    以这道题目为例:

    CF707D Persistent Bookcase

    题意搬运:

    维护一个二维零一矩阵((n,m<=1000)),支持四种操作(不超过(10^5)次):

    • ((i,j))置一
    • ((i,j))置零
    • 将第i行(01)反转
    • 回到第(K)次操作后的状态((K)可以为(0))
    • 每次操作后输出全局一共有多少个(1)

    思路

    emmmm,感觉很像某种可持久化数据结构,但是蒟蒻实在想不出来该用啥。

    But,操作树可以很轻松的解决这个问题。

    这个问题没有强制在线

    对于一个操作(i)我们发现倘若它不是第四种操作,它可以从自己的上一个操作转移来。

    因为你是沿用的上一次操作的状态来对当前状态做出修改。

    我们实际上要运用上一次修改后的状态进行修改。

    那么对于非操作4的操作,我们想到建一条边(i-1,i),表示操作(i-1)的状态可以转移到操作(i),也就是操作(i)仍然需要它的状态。

    实际上对于操作(4)也是一样的,我们连一条边(K,i),不需要连(i-1,i),这样即可。因为操作4只需要知道第(K)次操作后的状态,于是我们用操作(K)的状态来转移。

    我们来模拟一下建树的过程:

    先是状态0,全部都是0,没有修改之前。

    然后有了操作1,它沿用的操作0的状态(即初始状态)

    接着有了操作二,它需要沿用操作(1)的状态

    再是操作三,它沿用操作2的状态:

    后来是操作4,注意,它属于操作类型4,它需要访问第1次修改后的状态,于是将操作4连到操作1下面:

    然后又来了一个操作5,这个操作应该沿用操作4的状态,于是连到操作4的下面。

    最后是操作6,注意,它属于操作类型4,它需要沿用操作3的状态,于是将操作6连到操作3的下面

    然后就建树完成了!(这是最核心的一部分)

    怎么获得答案?对这棵树进行(DFS),对于操作1,2,3就暴力修改,用(bitset)即可,然后对于操作4我们不需要修改,直接沿用前面的状态即可。

    遍历完后记得回溯,具体看代码。

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 100005,MAXM = 1005;
    bitset <MAXM> All,P[MAXM];//All全部为1,便于对一整行取反
    
    int n , m , Q;
    int start[MAXN],cnt = 0;
    int Ans[MAXN];
    
    struct Node {
    	int op,x,y,id;
    } T[MAXN];
    
    struct Edge {
    	int next,to;
    } edge[MAXN];
    
    void add(int from,int to)
    {
    	cnt ++;
    	edge[cnt].to = to;
    	edge[cnt].next = start[from];
    	start[from] = cnt;
    	return ;
    }
    
    void DFS(int x,int from,int Now)//x表示当前节点,from表示当前节点的父亲节点,Now表示的是当前状态的答案
    {
    	bool q = 0;
    	if(T[x].op == 1)
    	{
    		q = P[T[x].x][T[x].y];
    		if(P[T[x].x][T[x].y] == 0)Now ++;//更新答案
    		P[T[x].x][T[x].y] = 1;//直接修改
    	}
    	if(T[x].op == 2)
    	{
    		q = P[T[x].x][T[x].y];
    		if(P[T[x].x][T[x].y] == 1)Now --;//更新答案
    		P[T[x].x][T[x].y] = 0;
    	}
    	if(T[x].op == 3)
    	{
    		int A = P[T[x].x].count();//原来有多少个1
    		P[T[x].x] ^= All;//暴力修改
    		Now += P[T[x].x].count() - A;//统计现在的1的个数减去原来1的个数即是答案
    	}
          //对于操作4不需要做出修改,直接沿用上次操作的答案即可
    	Ans[T[x].id] = Now;//将答案塞进答案序列
    	for(int i = start[x] ; i ; i = edge[i].next)
    	{
    		int to = edge[i].to;
    		if(to != from)DFS(to,x,Now);//遍历
    	}
    	if(T[x].op <= 2)P[T[x].x][T[x].y] = q;//状态回溯
    	if(T[x].op == 3)P[T[x].x] ^= All;//状态回溯
    	return ;
    }
    
    inline int read()
    {
    	int x = 0, flag = 1;
    	char ch = getchar();
    	for( ; ch > '9' || ch < '0' ; ch = getchar())if(ch == '-')flag = -1;
    	for( ; ch >= '0' && ch <= '9' ; ch = getchar())x = (x << 3) + (x << 1) + ch - '0';
    	return x * flag;
    }
    
    int main()
    {
    	n = read() , m = read() ,Q = read();
    	for(int i = 1 ; i <= m ; i ++)All[i] = 1;
    	for(int i = 1 ; i <= n ; i ++)P[i].reset();//全部置0
    	T[0].x = T[0].y = 0;
    	for(int i = 1 ; i <= Q ; i ++)
    	{
    		T[i].op = read();T[i].id = i;//离线操作的常用套路,记录它的第几个询问
    		if(T[i].op <= 2)T[i].x = read(),T[i].y = read();
    		else T[i].x = read();
    		if(T[i].op <= 3)add(i - 1 , i);//如果不属于操作4就连边i-1 to i
    		else add(T[i].x,i);//如果属于操作4就连边K to i
    	}
    	DFS(0,0,0);//注意要从0开始遍历,因为K可以等于0!
    	for(int i = 1 ; i <= Q ; i ++)cout << Ans[i] << endl;//输出答案即可
    	return 0;
    }
    

    相信你们看了例子就学会建树了,就写到这里了。

  • 相关阅读:
    Windows下搭建JSP开发环境
    ssh 学习笔记
    18 11 27 长连接 短链接
    18 11 26 用多进程 多线程 携程 实现 http 服务器的创建
    18 11 24 简单的http服务器
    关于 某个智慧树课堂的 机器与机器交流方法
    18 11 23 正则学习
    尝试解决 : Microsoft Visual C++ 14.0 is required 的问题
    18 11 20 网络通信 ----多任务---- 携程 ----生成器
    18 11 20 网络通信 ----多任务---- 携程 ----迭代器
  • 原文地址:https://www.cnblogs.com/MYCui/p/13997956.html
Copyright © 2011-2022 走看看