数据结构
Tags:Noip前的大抱佛脚
知识点及其应用
线段树
注意:空间开4倍
神奇标记
From8.26 Test_zsy(CPU监控)
如果一个点权为(val)的点被打上了((a,b))标记,那么他的实际点权为(max(a+val,b))
干啥滴?
标记不下放
- 区间加标记不下放,维护区间max或者最大值
方法是当前(tag)维护当前区域标记,(t)维护左右儿子的(max+tag[now]),并没有快多少,如果仍然忘记见提交记录
并查集
维护二分图
并查集每个点维护是否要改颜色,然后按秩合并/按大小合并即可
实际上可以说是用期望树高为(logn)的树形结构维护信息
维护后继位置
快速得到后继位置
-
[BZOJ2054]疯狂的馒头 维护数列的后继位置
对一个数列进行(10^7)次区间覆盖,查询最终颜色。
倒序做,每次做完一个区间把([l,r])的并查集父亲指向(r+1),表示扫到该区间,要从(r+1)开始染色,这样就可以快速得到下一个染色的位置,复杂度为(O(n))
-
[BZOJ2238]Mst 维护树形结构的后继位置
对树上路径进行染色,查询最终颜色。
路径拆成直上直下的两条,(x,lca、y,lca),于是每次弄完把([x,lca])的并查集父亲指向(fa[lca]),就可以类似数列一样快速求得下一个位置了,复杂度为(O(n))
堆
对于所有的父子结构,一定都有父亲的优先级大于左右儿子的优先级
可并堆的可持久化
左偏树可持久化,每次合并两个堆,只需要给经过的(log)个结点复制一遍就好了
#define lc H[x].ch[0]
#define rc H[x].ch[1]
struct heap {int ch[2],dis;double w;}H[M];
int Merge(int x,int y)
{
if(!x||!y) return x+y;
H[++node]=H[x];x=node;
if(H[x].w>H[y].w) swap(x,y);
rc=Merge(rc,y);
if(H[lc].dis<H[rc].dis) swap(lc,rc);
H[x].dis=H[rc].dis+1;
return x;
}
这个可以用来完成(k)短路问题
dsu on tree
姑且叫它数据结构
方式&原理
步骤:
- 先递归做轻儿子的答案,再递归做重儿子的答案
- 暴力把当前结点的轻儿子扫一遍统计答案
- 如果当前结点是递归下来的轻儿子,扫一遍把答案清空,否则不做处理
要求: 离线算法
复杂度: (O(nlogn))
分析:只需要考虑每个点被扫的次数,树剖下来每个点到根的路径最多有(log)条链,每逢轻重链交替的时候就会暴力去扫,于是每个点最多只会被扫(log)次,复杂度得证
适用范围
适用一些子树信息不好统计的题,可以类比于树上莫队
- 统计子树内数量最多的颜色的编号之和(Codeforces600E)
- 统计子树内距离(i)点不超过(k)的点的颜色数/某权值之和(BZOJ4771七彩树离线版/[湖南集训]谈笑风生)
单调队列
尺取合法区间
这大概是一个常见用法。
丢一道萝卜的题:长度为(n)的序列,每个数是([l[i],r[i]])内的一个值,求可能的LIS长度。
解法:当新加入的区间右端点大于前面所有区间的左端点时,产生矛盾。维护一个左端点的下降队列即可。
模板库
线段树
【模板】线段树 2
就是标记的优先顺序
const int N=1e5+10;
int n,m,t[N<<2],t1[N<<2],t2[N<<2],P;
void mul(int &x,int y) {x=1ll*x*y%P;}
void put(int x,int k,int op,int l,int r)
{
if(op==1) (t[x]+=1ll*(r-l+1)*k%P)%=P,(t2[x]+=k)%=P;
else mul(t[x],k),mul(t2[x],k),mul(t1[x],k);
}
void pushdown(int x,int l,int mid,int r)
{
int &s1=t1[x],&s2=t2[x];
if(s1!=1) put(x<<1,s1,2,l,mid),put(x<<1|1,s1,2,mid+1,r),s1=1;
if(s2!=0) put(x<<1,s2,1,l,mid),put(x<<1|1,s2,1,mid+1,r),s2=0;
}
void Add(int now,int l,int r,int gl,int gr,int op,int k)
{
if(l>=gl&&r<=gr) {put(now,k,op,l,r);return;}
int mid=(l+r)>>1;
pushdown(now,l,mid,r);
if(gl<=mid) Add(now<<1,l,mid,gl,gr,op,k);
if(gr>mid) Add(now<<1|1,mid+1,r,gl,gr,op,k);
t[now]=(t[now<<1]+t[now<<1|1])%P;
}
int Query(int now,int l,int r,int gl,int gr)
{
if(l>=gl&&r<=gr) return t[now];
int mid=(l+r)>>1,res=0;
pushdown(now,l,mid,r);
if(gl<=mid) res=Query(now<<1,l,mid,gl,gr);
if(gr>mid) res+=Query(now<<1|1,mid+1,r,gl,gr);
return res%P;
}
int main()
{
cin>>n>>m>>P;
for(int i=1;i<=n*4;i++) t1[i]=1;
for(int i=1,x;i<=n;i++) cin>>x,Add(1,1,n,i,i,1,x);
for(int i=1,x,y,k,op;i<=m;i++)
{
cin>>op>>x>>y;
if(op==1) cin>>k,Add(1,1,n,x,y,2,k);
if(op==2) cin>>k,Add(1,1,n,x,y,1,k);
if(op==3) cout<<Query(1,1,n,x,y)<<endl;
}
return 0;
}
点分治
一种分治思想,解决树上链的问题
const int N=21000;
struct edge{int next,to,w;}a[N];
int n,m,K[N],head[N],cnt,siz[N],rt,mx[N];
int sum,ans[N],vis[N],P[N],cc;
map<int,int> Map;
void Getroot(int x,int fr)
{
siz[x]=1;mx[x]=0;
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;if(R==fr||vis[R]) continue;
Getroot(R,x);siz[x]+=siz[R];
mx[x]=max(mx[x],siz[R]);
}
mx[x]=max(mx[x],sum-siz[x]);
if(mx[x]<mx[rt]) rt=x;
}
void calcsum(int x,int fr)
{
sum++;
for(int i=head[x];i;i=a[i].next)
if(a[i].to!=fr&&!vis[a[i].to]) calcsum(a[i].to,x);
}
void Getdis(int x,int d,int fr)
{
P[++cc]=d;
for(int i=head[x];i;i=a[i].next)
if(!vis[a[i].to]&&a[i].to!=fr)
Getdis(a[i].to,d+a[i].w,x);
}
void solve(int x)
{
rt=0;mx[0]=1e9;
sum=0;calcsum(x,0);
Getroot(x,0);
vis[x=rt]=1;Map.clear();
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;if(vis[R]) continue;
cc=0;Getdis(R,a[i].w,x);
for(int j=1;j<=m;j++)
for(int w=1;w<=cc;w++)
if(Map.find(K[j]-P[w])!=Map.end()) ans[j]=1;
for(int w=1;w<=cc;w++) Map[P[w]]=1;
}
for(int j=1;j<=m;j++) if(Map.find(K[j])!=Map.end()) ans[j]=1;
for(int i=head[x];i;i=a[i].next)
if(!vis[a[i].to]) solve(a[i].to);
}
int main()
{
cin>>n>>m;
for(int i=1;i<n;i++)
{
int x,y,w;scanf("%d%d%d",&x,&y,&w);
a[++cnt]=(edge){head[x],y,w};head[x]=cnt;
a[++cnt]=(edge){head[y],x,w};head[y]=cnt;
}
for(int i=1;i<=m;i++) cin>>K[i];
solve(1);
for(int i=1;i<=m;i++) ans[i]?puts("AYE"):puts("NAY");
}