codeforces 502 g The Tree
题解:
一道优秀的题目
有几种做法:
1.维护后缀和
刚开始我想的是维护前缀和
然后用$sum[x]-sum[y]>=dep[x]-dep[y]$来做
但是这样子树赋值为0这个操作就很难进行了
因为你查找的是链上最小值,所以不改子树上面的节点是做不了的
那我们换一种方式,单点改,查询区间最大后缀和
这样子树染白的时候我们就可以在x处减去一个上面的最大后缀和,那么就对子树没有影响了
然后再把子树清空一下就可以
其实这个东西就是动态dp。。。
$$f(v)=max(f(x)-1,0)+v[v]$$
这个只用查询链
所以我们只需要维护$f[v]=MAX(f[top]必选时的最大值k1,任意情况最大值k2)$就可以了(v都是必选点)
#include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for(int i=h;i<=t;i++) #define dep(i,t,h) for(int i=t;i>=h;i--) #define ll long long #define me(x) memset(x,0,sizeof(x)) #define mep(x,y) memcpy(x,y,sizeof(y)) #define mid ((h+t)>>1) namespace IO{ char ss[1<<24],*A=ss,*B=ss; IL char gc() { return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++; } template<class T> void read(T &x) { rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48); while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f; } char sr[1<<24],z[20]; ll Z,C1=-1; template<class T>void wer(T x) { if (x<0) sr[++C1]='-',x=-x; while (z[++Z]=x%10+48,x/=10); while (sr[++C1]=z[Z],--Z); } IL void wer1() { sr[++C1]=' '; } IL void wer2() { sr[++C1]='\n'; } template<class T>IL void maxa(T &x,T y) {if (x<y) x=y;} template<class T>IL void mina(T &x,T y) {if (x>y) x=y;} template<class T>IL T MAX(T x,T y){return x>y?x:y;} template<class T>IL T MIN(T x,T y){return x<y?x:y;} }; using namespace IO; const int N=2.1e5; struct re{ int a,b; }e[N*2]; int head[N],l,n,m; IL void arr(int x,int y) { e[++l].a=head[x]; e[l].b=y; head[x]=l; } struct re2{ int a[2]; re2() { a[0]=a[1]=0; } re2(int x,int y) {a[0]=x; a[1]=y;}; re2 operator *(const re2 &o) const { re2 c; c.a[0]=a[0]+o.a[0]; c.a[1]=MAX(a[1]+o.a[0],o.a[1]); return c; } }; int fa[N],num[N],son[N],dfn[N],top[N],cnt; void dfs(int x,int y) { num[x]=1; fa[x]=y; for (rint u=head[x];u;u=e[u].a) { int v=e[u].b; if (v!=y) { dfs(v,x); num[x]+=num[v]; if (num[son[x]]<num[v]) son[x]=v; } } } void dfs1(int x,int y,int z) { top[x]=y; dfn[x]=++cnt; if (son[x]) dfs1(son[x],y,x); for (rint u=head[x];u;u=e[u].a) { int v=e[u].b; if (v!=z&&v!=son[x]) { dfs1(v,v,x); } } } struct sgt{ re2 sum[N*4]; bool lazy[N*4]; #define updata(x) sum[x]=sum[x*2]*sum[x*2+1] IL void down(int x,int h,int t) { if (lazy[x]) { lazy[x*2]=lazy[x*2+1]=1; lazy[x]=0; sum[x*2]=re2(-(mid-h+1),-1); sum[x*2+1]=re2(-(t-mid),-1); } } void build(int x,int h,int t) { if (h==t) { sum[x]=re2(-1,-1); return;} build(x*2,h,mid); build(x*2+1,mid+1,t); updata(x); } void change(int x,int h,int t,int pos,int k) { if (h==t) { sum[x]=re2(k,k);return; } down(x,h,t); if (pos<=mid) change(x*2,h,mid,pos,k); else change(x*2+1,mid+1,t,pos,k); updata(x); } re2 query(int x,int h,int t,int h1,int t1) { if (h1<=h&&t<=t1) return sum[x]; down(x,h,t); if (h1<=mid&&mid<t1) return query(x*2,h,mid,h1,t1)*query(x*2+1,mid+1,t,h1,t1); else if (h1<=mid) return query(x*2,h,mid,h1,t1); else return query(x*2+1,mid+1,t,h1,t1); } void push(int x,int h,int t,int h1,int t1) { if (h1<=h&&t<=t1) { lazy[x]=1; sum[x]=re2(-(t-h+1),-1); return;} down(x,h,t); if (h1<=mid) push(x*2,h,mid,h1,t1); if (mid<t1) push(x*2+1,mid+1,t,h1,t1); updata(x); } }S; IL int query(int x) { int ans=-1; re2 p1=re2(0,-1); while (x) { re2 p=S.query(1,1,n,dfn[top[x]],dfn[x]); p1=p*p1; ans=max(ans,p1.a[1]); x=fa[top[x]]; } return ans; } int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); read(n); read(m); rep(i,2,n) { int x; read(x); arr(i,x); arr(x,i); } dfs(1,0); dfs1(1,1,0); S.build(1,1,n); rep(i,1,m) { int kk,x; read(kk); read(x); if (kk==1) { S.change(1,1,n,dfn[x],S.query(1,1,n,dfn[x],dfn[x]).a[0]+1); } if (kk==2) { S.push(1,1,n,dfn[x],dfn[x]+num[x]-1); int ans=query(x); S.change(1,1,n,dfn[x],-ans-2); } if (kk==3) { if (query(x)>=0) cout<<"black"<<endl; else cout<<"white"<<endl; } } return 0; }
2.操作分块
分块这个东西有的时候的确简单巧妙。。
但一般我也不会去想分块。。
这个是看了别人题解的。。
我们以每$\sqrt{n}$个元素分一组
对块内的操作,我们等待$\sqrt{n}$都做完了再把这$\sqrt{n}$个操作加入树中
对于当前的询问,我们只需要树内的$\sqrt{n}$个节点的信息就可以了
我们可以建立虚树维护
本来打算学树上分块的。。。但发现这东西并没有啥用
会线段树合并/dsu on tree/树上莫队 应该不会也没啥关系
像这种利用虚树的题目还是比较多的