Vijos P1448 校门外的树【多解,线段树,树状数组,括号序列法+暴力优化】
校门外的树
描述
校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的…… 如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作: K=1,K=1,读入l、r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同 K=2,读入l,r表示询问l~r之间能见到多少种树 (l,r>0)
格式
输入格式
第一行n,m表示道路总长为n,共有m个操作 接下来m行为m个操作
输出格式
对于每个k=2输出一个答案
样例1
样例输入1
5 4
1 1 3
2 2 5
1 2 4
2 3 5
样例输出1
1
2
限制
1s
提示
范围:20%的数据保证,n,m<=100 60%的数据保证,n <=1000,m<=50000 100%的数据保证,n,m<=50000
来源
dejiyu@CSC WorkGroup
分析:这题目从上午九点写到下午四点,历经七个小时的磨难,只为给大家提供最优质的方法!
这道题我用了三种方法去解决!
第一种:线段树【时间花费最长,也最伤脑的写法】,做法是将[a,b]种上一种树,这个修改操作影响的询问满足,
询问区间与[a,b]有交,转化为统计总修改数-与某询问交为空集的修改数
对于一个修改操作[l,r],与它为空集的询问[a,b]满足a∈[1,l-1]或者b∈[r+1,n]
用两棵线段树维护,修改[l,r],将第一棵的[1,l-1]区间+1,第二棵[r+1,n]区间+1
询问[a,b],答案为之前的修改数-(第一棵单点询问b+第二棵单点询问a)
代码中线段树结点的l,r其实就是两棵线段树。。。标记永久化
下面给出线段树的代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 const int N=500050;
4 int n,m;
5 inline int read()
6 {
7 int x=0,f=1;
8 char ch=getchar();
9 while(ch<'0'||ch>'9')
10 {
11 if(ch=='-')
12 f=-1;
13 ch=getchar();
14 }
15 while(ch>='0'&&ch<='9')
16 {
17 x=x*10+ch-'0';
18 ch=getchar();
19 }
20 return x*f;
21 }
22 inline void write(int x)
23 {
24 if(x<0)
25 {
26 putchar('-');
27 x=-x;
28 }
29 if(x>9)
30 {
31 write(x/10);
32 }
33 putchar(x%10+'0');
34 }
35 struct Tree
36 {
37 int l,r;
38 int left,right;
39 }tree[N<<3];
40 inline void buildtree(int x,int y,int pos)
41 {
42 tree[pos].left=x;
43 tree[pos].right=y;
44 if(x==y)
45 {
46 return;
47 }
48 int mid=(x+y)/2;
49 buildtree(x,mid,pos*2);
50 buildtree(mid+1,y,pos*2+1);
51 }
52 inline void insertl(int x,int y,int pos)
53 {
54 int l=tree[pos].left;
55 int r=tree[pos].right;
56 if(l==x&&r==y)
57 {
58 tree[pos].l++;
59 return;
60 }
61 int mid=(l+r)/2;
62 if(y<=mid)
63 insertl(x,y,pos*2);
64 else if(x>mid)
65 insertl(x,y,pos*2+1);
66 else
67 {
68 insertl(x,mid,pos*2);
69 insertl(mid+1,y,pos*2+1);
70 }
71 }
72 inline void insertr(int x,int y,int pos)
73 {
74 int l=tree[pos].left;
75 int r=tree[pos].right;
76 if(l==x&&r==y)
77 {
78 tree[pos].r++;
79 return;
80 }
81 int mid=(l+r)/2;
82 if(y<=mid)
83 insertr(x,y,pos*2);
84 else if(x>mid)
85 insertr(x,y,pos*2+1);
86 else
87 {
88 insertr(x,mid,pos*2);
89 insertr(mid+1,y,pos*2+1);
90 }
91 }
92 inline int askl(int k,int x)
93 {
94 int l=tree[k].left;
95 int r=tree[k].right;
96 if(l==r)
97 return tree[k].l;
98 int mid=(l+r)/2;
99 if(x<=mid)
100 return tree[k].l+askl(k*2,x);
101 else return tree[k].l+askl(k*2+1,x);
102 }
103 inline int askr(int k,int x)
104 {
105 int l=tree[k].left;
106 int r=tree[k].right;
107 if(l==r)
108 return tree[k].r;
109 int mid=(l+r)/2;
110 if(x<=mid)
111 return tree[k].r+askr(k*2,x);
112 else return tree[k].r+askr(k*2+1,x);
113 }
114 int main()
115 {
116 n=read();
117 m=read();
118 int tot=0;
119 buildtree(0,n,1);
120 for(int i=1;i<=m;i++)
121 {
122 int t,a,b;
123 cin>>t>>a>>b;
124 if(t==1)
125 {
126 insertl(0,a-1,1);
127 insertr(b+1,n,1);
128 tot++;
129 }
130 else
131 {
132 int ans=askr(1,a)+askl(1,b);
133 write(tot-ans);
134 cout<<endl;
135 }
136 }
137 return 0;
138 }
第二种写法:树状数组
做法:这题是一条条线段,所以我们可以用线段树之类的东东来实现,然后感觉树状数组写起来简单一点所以就打了 开两个数组来存一个是开始的点的数量,一个是结束的 ,然后随便搞一下,最后输出就可以了
下面给出树状数组写法:
1 #include <bits/stdc++.h>
2 using namespace std;
3 const int N=50050;
4 int l[N],r[N];
5 int n,m;
6 inline int read()
7 {
8 int x=0,f=1;
9 char ch=getchar();
10 while(ch<'0'||ch>'9')
11 {
12 if(ch=='-')
13 f=-1;
14 ch=getchar();
15 }
16 while(ch>='0'&&ch<='9')
17 {
18 x=x*10+ch-'0';
19 ch=getchar();
20 }
21 return x*f;
22 }
23 inline void write(int x)
24 {
25 if(x<0)
26 {
27 putchar('-');
28 x=-x;
29 }
30 if(x>9)
31 {
32 write(x/10);
33 }
34 putchar(x%10+'0');
35 }
36 int lowbit(int x)
37 {
38 return x&-x;
39 }
40 void add(int x,int d,int c[])
41 {
42 while(x<=n)
43 {
44 c[x]+=d;
45 x+=lowbit(x);
46 }
47 }
48 int sum(int x,int c[])
49 {
50 int s=0;
51 while(x>0)
52 {
53 s+=c[x];
54 x-=lowbit(x);
55 }
56 return s;
57 }
58 int main()
59 {
60 int k,x,y;
61 n=read();
62 m=read();
63 for(int i=1;i<=m;i++)
64 {
65 cin>>k>>x>>y;
66 if(k==1)
67 {
68 add(x,1,l);
69 add(y,1,r);
70 }
71 else
72 {
73 write(sum(y,l)-sum(x-1,r));
74 cout<<endl;
75 }
76 }
77 return 0;
78 }
第三种方法:括号序列法【简称括号法】
假设有一个长度为10的数轴,我们要将区间[ 2 , 5 ]中种树,这时,我们将 2 处放一个左括号 " ( " ,5处放一个 " )" ,表示区间 [ 2 , 5 ]种了树。
查询某个区间树的种类,如区间[ 3 , 10],只需统计10之前(包括10)有多少个‘(’,统计3之前有多少个‘)’,(不包括3)。
如下图所示:
以上就是括号序列的过程。简单的说,就是更新区间[a,b]时,点a记录左括号数,点b记录右括号数,查询区间[a,b]时,即为b之前(包括b)的左括号数-a之前的右括号数。
下面给出非常简练优秀的代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 const int N=50050;
4 int l[N],r[N];
5 int n,m;
6 inline int read()
7 {
8 int x=0,f=1;
9 char ch=getchar();
10 while(ch<'0'||ch>'9')
11 {
12 if(ch=='-')
13 f=-1;
14 ch=getchar();
15 }
16 while(ch>='0'&&ch<='9')
17 {
18 x=x*10+ch-'0';
19 ch=getchar();
20 }
21 return x*f;
22 }
23 inline void write(int x)
24 {
25 if(x<0)
26 {
27 putchar('-');
28 x=-x;
29 }
30 if(x>9)
31 {
32 write(x/10);
33 }
34 putchar(x%10+'0');
35 }
36 int main()
37 {
38 int k,x,y;
39 n=read();
40 m=read();
41 for(int i=1;i<=m;i++)
42 {
43 cin>>k>>x>>y;
44 if(k==1)
45 {
46 for(int j=x;j<=n;j+=j&-j)
47 l[j]++;
48 for(int j=y;j<=n;j+=j&-j)
49 r[j]++;
50 }
51 else
52 {
53 int ans=0;
54 for(int j=y;j;j-=j&-j)
55 ans+=l[j];
56 for(int j=x-1;j;j-=j&-j)
57 ans-=r[j];
58 write(ans);
59 cout<<endl;
60 }
61 }
62 return 0;
63 }
- 在Entity Framework中使用存储过程(一):实现存储过程的自动映射
- 在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义?
- 表单控件的副产品——查询控件
- 表单控件续(1)——应用接口来简化和分散代码
- 通过自定义配置实现插件式设计
- 让IoC动态解析自定义配置(提供基于Unity的实现)
- 如何让ASP.NET默认的资源编程方式支持非.ResX资源存储
- 在VS中通过建立依赖关系使文件结构更清晰
- 一个关于ConfigurationManager.GetSecion方法的小问题
- 追踪记录每笔业务操作数据改变的利器——SQLCDC
- 一个完整的用于追踪数据改变的解决方案
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- 通过内存分析工具来证明字符串驻留机制
- 如果在BackgroundWorker运行过程中关闭窗体…
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 谷歌开源NLP模型可视化工具LIT,模型训练不再「黑箱」
- MongoDB 案例:Document failed validation 错误
- 利用GoogleAppsScript自动回复短信实现保号
- 用php来查询graphql
- 利用树莓派的摄像模块实现“扫码枪”
- n ../../node_modules/@storybook/channels/dist/index.d.ts:25:9 - error TS1086: An accessor cannot ...
- 要不来重新认识Spring事务?三歪又学到了
- 读者问:学完SSM,该学什么呢?
- go-zero 微服务框架介绍
- redis-cli 未找到命令的一个解决方式
- 【每日一题】42. Trapping Rain Water
- iframe跨域安全
- Efficiently traversing InnoDB B+Trees with the page directory (9.利用页目录实现对B+树的高效遍历)
- C语言 | 每日基础(37)
- 《求求大厂给个Offer》Map面试题