线段树
count colors
特别的地方:“小线段”和区间端点的区别,和离散化maylor’s poster有点像,在操作的时候要缩小区间,注意题目给我们的是染色区间的端点,就是如果对[3,9]进行操作 ,只能是对[3,8]操作或者是对[4,9]操作,我选第一种,我用左一个单位的左端点储存这个单位的颜色,比如[3,9]段的颜色变成1,那就是以3为左端点单位段变成1,tr[3].lz=1
,tr[4].lz=1
…一直到tr[8].lz=1
,注意,因为9已经是右边端点了,9储存的是[9,10]的颜色,所以tr[9].lz!=1
。所以在写代码就要把右区间都减少1.
#include<cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=8000+7;
struct node{
int l,r,lz;///这个时候lz和val的值完全等同 不需要再添加新的变量
// int val;
}tr[N<<2];
int vis[N],n,val[N],pre=-1;//因为区间从0开始 所以应该置为-1
void push_down(int k){
if(tr[k].lz!=-1)///以后在这里完成判断!
tr[k<<1].lz=tr[k<<1|1].lz=tr[k].lz;
tr[k].lz=-1;//记得置为-1,否则在query下传的时候在更早时候的值会下传把后来的值覆盖掉
}
void build(int k,int l,int r){
tr[k].l=l,tr[k].r=r;tr[k].lz = -1;
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int num){
if(tr[k].l>=l&&tr[k].r<=r){
tr[k].lz=num;
return;
}
if(tr[k].lz!=-1)push_down(k);
if(tr[k<<1].r>=l)update(k<<1,l,r,num);///不需要push_up的原因是 如果更新到这里 那么第一个if是不满足的 也就是当前的k并不是(l,r)区间的子集 所以这时候去push_up是错误的
if(tr[k<<1|1].l<=r)update(k<<1|1,l,r,num);
}
void query(int k,int l,int r){
if(tr[k].l==tr[k].r){
if(tr[k].lz!=-1&&tr[k].lz!=pre)vis[tr[k].lz]++;///pre用来表示之前查找到的颜色 因为一段只能算一次
// 一开始理解错题意以为是一块就加1 其实是一段就加1 如果上一次的那一段的颜色和当前相同那么它们是同一段
pre=tr[k].lz;
return;
}
push_down(k);
int mid=(tr[k].l+tr[k].r)>>1;
if(l<=mid) query(k<<1,l,r);
if(r>mid)query(k<<1|1,l,r);
}
int main(){
while(scanf("%d",&n)==1){
pre=-1;
memset(vis,0,sizeof vis);
build(1,0,7999);
for(int i=1;i<=n;i++){
int l,r;
scanf("%d%d%d",&l,&r,&val[i]);
update(1,l,r-1,val[i]);
}
query(1,0,7999);///因为是“块”,所以需要缩小区间 否则会覆盖
for(int i=0;i<=8000;i++){
if(vis[i])printf("%d %d\n",i,vis[i]);
}
printf("\n");
}
}
区间根号
很厉害的就是最后所有值变成1的时候就不用继续操作了,其实不是特别难想,当你没有什么思路就直接模拟一下题目的操作,总会到最后一步的。可以用区间和来判断最后到达1 ,因为都不会变成0,线段树真的好强啊!
#include<cstdio>
#include <algorithm>
#include<cmath>
#include<iostream>
#define ll long long
using namespace std;
const int N=1E5+7;
struct node{
int l,r;
ll sum;
}tr[N<<2];
int n;
void push_up(int k){tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;}
void build(int k,int l ,int r){
tr[k].l=l,tr[k].r=r;
if(l==r){
ll tmp;
scanf("%lld",&tmp);
tr[k].sum=tmp;
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
push_up(k);
}
void update(int k,int l,int r){
if(tr[k].sum==(tr[k].r-tr[k].l+1))//判断最后变成1
return ;
if(tr[k].l==tr[k].r){
tr[k].sum=floor(sqrt(tr[k].sum));
return;
}
if(tr[k<<1].r>=l)update(k<<1,l,r);
if(tr[k<<1|1].l<=r)update(k<<1|1,l,r);
push_up(k);
}
ll query(int k,int l,int r){
if(tr[k].l>=l&&tr[k].r<=r){
return tr[k].sum;
}ll ans=0;
if(tr[k<<1].r>=l)ans+=query(k<<1,l,r);
if(tr[k<<1|1].l<=r)ans+=query(k<<1|1,l,r);
return ans;
}
int main(){
int cnt=0,m;
while(~scanf("%d",&n)){
printf("Case #%d:\n",++cnt);
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(l>r)swap(l,r);//被恶心到了但不是第一次,最好看清题意,不要主观啊!
if(op==0){
update(1,l,r);
}
else {
printf("%lld\n",query(1,l,r));
}
}
printf("\n");
}
return 0;
}
维护最大最小值
直接区间查询
#include<cstdio>
#include<algorithm>
using namespace std;
/*
* 首先是构建一棵树 其次是要维护的是区间的最大数和最小数
* 最后是QUERY求出最大和最小的差
* 看我乱糟糟的代码就知道根本不理解线段树,push_down是区间修改才需要用到,
* 而且总区间也没有看清楚,以为n就是范围了。以后看题以为看懂了再看一遍,
* 把每一个变量的含义弄清楚。
* */
const int N=50007;
struct node{
int maxx,minn,l,r,lzx,lzn;
}tr[N<<2];
int n,m,a[N],ma=0,mi=0x3f3f3f3f;
int ans;
/*void push_down(int k){
tr[k<<1].lzn=tr[k<<1|1].lzn=tr[k].lzn;
tr[k<<1].lzx=tr[k<<1|1].lzx=tr[k].lzx;
tr[k<<1].maxx=tr[k<<1|1].maxx=tr[k].maxx;
tr[k<<1].minn=tr[k<<1|1].maxx=tr[k].maxx;
tr[k].lzn=tr[k].lzx=0;
}*/
void push_up(int k){
tr[k].maxx=max(tr[k<<1].maxx,tr[k<<1|1].maxx);
tr[k].minn=min(tr[k<<1].minn,tr[k<<1|1].minn);
}
///根本不用下传标记 下传标记是在区间修改的时候 但是现在你已经直接获得了
/// 从树的顶端走到最下端要O(m*n*logn),现在的赋初值需要O(n*logn)
void build(int k,int l,int r){
tr[k].l=l,tr[k].r=r;
if(l==r) {
int tmp;
scanf("%d",&tmp);
tr[k].minn=tr[k].maxx=tmp;
return; }
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
push_up(k);
}
/*void update(int k,int l,int r,int num){
if(tr[k].l>=l&&tr[k].r<=r){
tr[k].maxx=max(tr[k].maxx,num);
tr[k].minn=min(tr[k].minn,num);
*//*tr[k].lzn=tr[k].minn;
tr[k].lzx=tr[k].maxx;*//*
return;
}
*//* push_down(k);*//*
update(k<<1,l,r,num);
if(tr[k<<1|1].l<=r)update(k<<1|1,l,r,num);
push_up(k);
}*/
void query(int k,int l,int r){
if(tr[k].l>=l&&tr[k].r<=r){
ma=max(ma,tr[k].maxx);
mi=min(mi,tr[k].minn);
return ;
}
if(tr[k<<1].r>=l)query(k<<1,l,r);
if(tr[k<<1|1].l<=r)query(k<<1|1,l,r);
}
int main(){
scanf("%d%d",&n,&m);
build(1,1,n);
for(int i=1;i<=m;i++){
int l,r;
ans=0;
scanf("%d%d",&l,&r);
ma=-1,mi=0x3f3f3f3f;
query(1,l,r);
///QUERY之后直接获取当前的最大值和最小值即可
// printf("%d\n",ans);
printf("%d\n",ma-mi);
}return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include <algorithm>
#include<string>
using namespace std;
#define in(n) scanf("%d",&n);
#define ch(n) scanf("\n%c",&n);///我不会说因为这个错误”\n “没有加我把这段代码打了四次的
#define re(i,n) for(int i=1;i<=n;i++)
const int N=50010;
struct TREE{
int l,r,ll,rr,sum;
}tr[N<<2];
int n,m,de[N];
void build(int k,int l,int r){
tr[k].l=l,tr[k].r=r,tr[k].ll=tr[k].rr=tr[k].sum=(r-l+1);//ll表示当前节点从左端点开始向右的最大连续子区间,rr同理 sum在下面的操作不会更新 只是用来判断当前节点是否为连续和用来更新ll和rr的值
///一开始打错成r+l-1,结果当然比预想的大很多
///所以我应该思考哪里变大 从所有值更改的情况查找
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
// cout<<"build"<<k<<l<<r<<' '<<tr[k].ll<<' '<<tr[k].rr<<endl;
}
void update(int k,int pos,int num){
if(tr[k].l==tr[k].r){tr[k].ll=tr[k].rr=num;return;}
int mid=(tr[k].l+tr[k].r)>>1;
if(pos<=mid)update(k<<1,pos,num);
else update(k<<1|1,pos,num);
//这几个选择结构体现了线段树两个节点之间的区间连续这个特点
//如果当前节点总的连续区间==当前节点左孩子节点从左端点向右的最大连续区间+当前节点右孩子节点从右端点向左的最大连续区间 说明当前节点是连续的
// 注意一个节点(k)分下来的两个节点(k<<1),(k<<1|1)的区间和(先不管是否连续)一定是(k)节点的总区间 并且分下来的两个节点的区间不互相重叠
if(tr[k].sum==tr[k<<1].ll+tr[k<<1|1].rr)tr[k].ll=tr[k].rr=tr[k].sum;
else{
if(tr[k<<1].ll==tr[k<<1].sum)tr[k].ll=tr[k<<1].sum+tr[k<<1|1].ll;
else tr[k].ll=tr[k<<1].ll;
if(tr[k<<1|1].rr==tr[k<<1|1].sum)tr[k].rr=tr[k<<1|1].sum+tr[k<<1].rr;
else tr[k].rr=tr[k<<1|1].rr;
}//cout<<"in"<<k<<endl<<tr[k].ll<<endl;
}
int query(int k,int pos){
if(k==1){
if(tr[k].l+tr[k].ll-1>=pos)return tr[k].ll;
if(tr[k].r-tr[k].rr+1<=pos)return tr[k].rr;
}
if(tr[k].l+tr[k].ll-1>=pos)return tr[k].ll+tr[k-1].rr;//如果出现[k-1]是在当前节点的上一层,那么当前节点是整棵树的左端点,如果它存在 就一定会被(k==1)的情况覆盖掉
//在上一个判断就已经return了 所以[k-1]只会和[k]同层 如果然后为什么加上左边或者右边就足够了呢?假设除了这两个节点还有其他节点的区间和它们相连,那么它们肯定拥有共同的父亲节点
//那么它们在父亲节点就已经被更新完毕了 所以不会查询到当前这一层
if(tr[k].r-tr[k].rr+1<=pos)return tr[k].rr+tr[k+1].ll;
if(tr[k].l==tr[k].r)return 0;
int mid=(tr[k].l+tr[k].r)>>1;
if(pos<=mid)return query(k<<1,pos);
else return query(k<<1|1,pos);
}
int main(){
while(~scanf("%d%d",&n,&m)){
memset(tr,0,sizeof tr);
memset(de,0,sizeof de);
build(1,1,n);
int cnt=0;
re(i,m){
char op[9];
cin>>op;
if(op[0]=='R'&&cnt){
update(1,de[cnt--],1);
}
else {
int x;in(x)///其实在这里调试的时候print了两次 绝对是进入了两次循环 所以这个时候判断一下进入条件就可以了!
///学会一步步推理和窄化问题
if(op[0]=='D'){
de[++cnt]=x;
update(1,x,0);
}
else printf("%d\n",query(1,x));
}
// for(int j=1;j<=n*4;j++)printf("%d %d %d %d %d\n",j,tr[j].l,tr[j].r,tr[j].ll,tr[j].rr);printf("\n");
}
}
return 0;
}
dfs序
从祖先开始dfs,给每一个人一个开始和结束的时间戳,标记他从开始进入dfs的序号和跳出dfs的序号。
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#define ll long long
using namespace std;
const int N=5E4+7;
vector<int>to[N];
int n,m,cnt,st[N],ed[N],vis[N];
struct node{
int l,r;
ll task,lz;
}tr[N<<2];
void init(){
memset(tr,0,sizeof tr);
memset(st,0,sizeof st);
memset(ed,0,sizeof ed);
memset(to,0,sizeof to);
for(int i=1;i<=n;i++)to[i].clear();
memset(vis,0,sizeof vis);
cnt=0;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
to[v].push_back(u);
vis[u]++;
}
}
void dfs(int i){
st[i]=++cnt;
for(int j=0;j<to[i].size();j++)dfs(to[i][j]);
ed[i]=cnt;
}
void build(int k,int l,int r){
tr[k].l=l,tr[k].r=r,tr[k].task=tr[k].lz=-1;
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void push_down(int k){
if(tr[k].lz==-1)return;
tr[k<<1].task=tr[k<<1|1].task=tr[k<<1].lz=tr[k<<1|1].lz=tr[k].lz;
tr[k].lz=-1;
}
void update(int k,int l ,int r,ll task){
if(l<=tr[k].l&&tr[k].r<=r){
tr[k].lz=tr[k].task=task;
return;
}push_down(k);
int mid=(tr[k].l+tr[k].r)>>1;//cout<<"update"<<k<<' '<<l<<' '<<r<<' '<<tr[k].l<<' '<<tr[k].r<<' '<<tr[k].task<<endl;
if(l<=mid)update(k<<1,l,r,task);
if(r>mid)update(k<<1|1,l,r,task);
}
ll query(int k,int l,int r){
if(l<=tr[k].l&&tr[k].r<=r)return tr[k].task;
push_down(k);
int mid=(tr[k].l+tr[k].r)>>1;//cout<<"query"<<k<<' '<<l<<' '<<r<<' '<<tr[k].l<<' '<<tr[k].r<<' '<<tr[k].task<<endl;
if(l<=mid)return query(k<<1,l,r);
if(r>mid)return query(k<<1|1,l,r);
}
int main(){
int t;
int num=0;
cin>>t;
while(t--){
printf("Case #%d:\n",++num);
scanf("%d",&n);
init();
build(1,1,n);
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs(i);
break;
}
}
for(int i=1;i<=n;i++)//cout<<st[i]<<' '<<ed[i]<<endl;
scanf("%d",&m);
while(m--){
char op[10];
int peo;cin>>op; scanf("%d",&peo);
if(op[0]=='C'){
printf("%lld\n",query(1,st[peo],ed[peo]));
}
else if(op[0]=='T'){
//cout<<peo<<st[peo]<<ed[peo]<<"dd"<<endl;
ll task;scanf("%lld",&task);
update(1,st[peo],ed[peo],task);
}
}
}
return 0;
}