首页 > 代码库 > Vijos P1448 校门外的树【多解,线段树,树状数组,括号序列法+暴力优化】
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 41 1 32 2 51 2 42 3 5
样例输出1
12
限制
1s
提示
范围:20%的数据保证,n,m<=100
60%的数据保证,n <=1000,m<=50000
100%的数据保证,n,m<=50000
来源
dejiyu@CSC WorkGroup
题目链接:https://vijos.org/p/1448
分析:这题目从上午九点写到下午四点,历经七个小时的磨难,只为给大家提供最优质的方法!
这道题我用了三种方法去解决!
第一种:线段树【时间花费最长,也最伤脑的写法】,做法是将[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 else131 {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 else72 {73 write(sum(y,l)-sum(x-1,r));74 cout<<endl;75 }76 }77 return 0;78 }
第三种方法:括号序列法【简称括号法】
以上就是括号序列的过程。简单的说,就是更新区间[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 else52 {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 }
Vijos P1448 校门外的树【多解,线段树,树状数组,括号序列法+暴力优化】