Codeforces 题目传送门 & 洛谷题目传送门

yyq:“hot tea 不常有,做过了就不能再错过了”

似乎这是半年前某场 hb 模拟赛的 T2?当时 ycx、ymx、yxh 都去打了,可我似乎那时候还在旅游?

不难发现,对于操作 \(i\) 实际上是将 \(a_i\) 与 \(b_i\) 子树的并集内的点的答案集合并上 \(a_i\) 与 \(b_i\) 子树的并集。于是我们考虑将询问离线下来并在树上进行一遍 DFS,动态地维护一个可重集 \(S\)。当我们第一次访问某个点 \(x\) 的时候就遍历所有 \(a_i=x\lor b_i=x\) 的操作 \(i\),并将 \(subtree(a_i)\cup subtree(b_i)\) 中所有点加入 \(S\),那么显然此时 \(S\) 中所有点都与 \(x\) 有相同的数,也就是说,如果 \(|S|=0\) 那么 \(ans_x=0\),否则 \(ans_x=|S|-1\)(要扣掉 \(x\) 本身),这个很好想通。回溯的时候就撤销全部在 \(x\) 处加入 \(S\) 的点即可。

我们不妨用 DFS 序的角度考虑这个问题。显然子树对应的 DFS 序是一段连续的区间 \([L_x,R_x]\)。而一个点 \(x\) 在 \(S\) 中出现的次数也可量化为 \(cnt_x\),故上述 DFS 的过程可翻译为以下三种操作:

  • 将一个点 \(x\) 子树内所有点加入可重集,i.e. \(\forall u\in[bg_x,ed_x],cnt_u\leftarrow cnt_u+1\)。
  • 将一个点 \(x\) 子树内所有点从可重集中删除,i.e. \(\forall u\in[bg_x,ed_x],cnt_u\leftarrow cnt_u-1\)。
  • 求可重集中有多少个不同的点,i.e. \(\sum\limits_{u=0}^n[cnt_u>0]\)

于是现在我们的任务就是怎样解决这个子问题。针对这个子问题给出了五种解法:

解法一:根号暴力。

u1s1 根号暴力 ta 不香吗?

考虑将整个序列每 \(B\) 个划分为一块。对于每一块我们开一个桶 \(buc_{i,j}\) 表示第 \(i\) 块中有多少个 \(u\) 满足 \(cnt_u=j\),当我们对某个区间进行整体 \(+v\) 的时候,我们就套路地采用边角块暴力,中间块打标记的方式,也就是边角块暴力地对 \(cnt_u,buc_u\) 进行修改,中间块维护一个 \(tag_i\) 并令 \(tag_i\leftarrow tag_i+v\)。查询时我们可拿总数 \(n\) 减去 \(cnt_i=0\) 的个数,这个又可以通过 \(\sum\limits cnt_{i,-tag_i}\) 求出。

时间复杂度 \(\dfrac{n^2}{B}+nB\),空间复杂度 \(\dfrac{n^2}{B}\),显然 \(B=\sqrt{n}\) 时候最优。

ycx 神仙说此题桶要开 short,可似乎开 int 也能过?

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define fi first
  4. #define se second
  5. #define fill0(a) memset(a,0,sizeof(a))
  6. #define fill1(a) memset(a,-1,sizeof(a))
  7. #define fillbig(a) memset(a,63,sizeof(a))
  8. #define pb push_back
  9. #define ppb pop_back
  10. #define mp make_pair
  11. template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
  12. template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
  13. typedef pair<int,int> pii;
  14. typedef long long ll;
  15. typedef unsigned int u32;
  16. typedef unsigned long long u64;
  17. namespace fastio{
  18. #define FILE_SIZE 1<<23
  19. char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
  20. inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
  21. inline void putc(char x){(*p3++=x);}
  22. template<typename T> void read(T &x){
  23. x=0;char c=getchar();T neg=0;
  24. while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
  25. while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. if(neg) x=(~x)+1;
  27. }
  28. template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
  29. template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
  30. void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
  31. }
  32. const int MAXN=1e5;
  33. const int SQRT=316;
  34. int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
  35. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  36. vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
  37. int blk,blk_cnt,L[SQRT+5],R[SQRT+5],bel[MAXN+5];
  38. int val[MAXN+5],tag[SQRT+5];short cnt[SQRT+5][MAXN+5];
  39. void dfs0(int x=1,int f=0){
  40. bgt[x]=++tim;
  41. for(int e=hd[x];e;e=nxt[e]){
  42. int y=to[e];if(y==f) continue;
  43. dfs0(y,x);
  44. } edt[x]=tim;
  45. }
  46. void add(int l,int r,int v){
  47. if(bel[l]==bel[r]){
  48. for(int i=l;i<=r;i++) cnt[bel[l]][val[i]]--;
  49. for(int i=l;i<=r;i++) val[i]+=v;
  50. for(int i=l;i<=r;i++) cnt[bel[l]][val[i]]++;
  51. } else {
  52. for(int i=l;i<=R[bel[l]];i++) cnt[bel[l]][val[i]]--;
  53. for(int i=l;i<=R[bel[l]];i++) val[i]+=v;
  54. for(int i=l;i<=R[bel[l]];i++) cnt[bel[l]][val[i]]++;
  55. for(int i=L[bel[r]];i<=r;i++) cnt[bel[r]][val[i]]--;
  56. for(int i=L[bel[r]];i<=r;i++) val[i]+=v;
  57. for(int i=L[bel[r]];i<=r;i++) cnt[bel[r]][val[i]]++;
  58. for(int i=bel[l]+1;i<=bel[r]-1;i++) tag[i]+=v;
  59. }
  60. }
  61. int query(){
  62. int ret=0;
  63. for(int i=1;i<=blk_cnt;i++) ret+=cnt[i][-tag[i]];
  64. return n-ret;
  65. }
  66. int ans[MAXN+5];
  67. void calc(int x=1,int f=0){
  68. for(int i=0;i<qv[x].size();i++){
  69. int y=qv[x][i];
  70. add(bgt[x],edt[x],1);
  71. add(bgt[y],edt[y],1);
  72. } ans[x]=query();
  73. if(ans[x]) ans[x]--;
  74. for(int e=hd[x];e;e=nxt[e]){
  75. int y=to[e];if(y==f) continue;
  76. calc(y,x);
  77. }
  78. for(int i=0;i<qv[x].size();i++){
  79. int y=qv[x][i];
  80. add(bgt[x],edt[x],-1);
  81. add(bgt[y],edt[y],-1);
  82. }
  83. }
  84. int main(){
  85. scanf("%d%d",&n,&m);
  86. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
  87. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
  88. blk=(int)pow(n,0.5);blk_cnt=(n-1)/blk+1;
  89. for(int i=1;i<=blk_cnt;i++){
  90. L[i]=(i-1)*blk+1;R[i]=min(i*blk,n);
  91. for(int j=L[i];j<=R[i];j++) bel[j]=i;
  92. cnt[i][0]=R[i]-L[i]+1;
  93. } dfs0();calc();
  94. for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
  95. return 0;
  96. }

解法二:线段树+标记永久化

u1s1 在做此题之前我还不知道啥是标记永久化,感谢这题让我学会了一个新算法

在上面的根号暴力中,我们没有用到此题一个很重要的性质,那就是我们肯定是先加入再撤销贡献的,也就是说任何时刻都有 \(\forall u,cnt_u\ge 0\)。

于是我们考虑这样一个思路,用线段树维护上面的操作。当我们执行线段树区间修改的时候,我们标记照样打,不过我们不下放标记(这是标记永久化与普通线段树的区别所在)。显然根据之前的推论任意时刻任意节点的标记都非负。线段树上每个节点维护一个值 \(val\) 表示该节点表示的区间中有多少个 \(cnt_u=0\),而如果我们发现某个区间的标记非负,就意味着该区间中所有 \(cnt_u\) 都 \(>0\),直接 \(val_i=0\) 即可。否则 \(val_i\) 就是其左右儿子 \(val\) 之和。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define fi first
  4. #define se second
  5. #define fill0(a) memset(a,0,sizeof(a))
  6. #define fill1(a) memset(a,-1,sizeof(a))
  7. #define fillbig(a) memset(a,63,sizeof(a))
  8. #define pb push_back
  9. #define ppb pop_back
  10. #define mp make_pair
  11. template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
  12. template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
  13. typedef pair<int,int> pii;
  14. typedef long long ll;
  15. typedef unsigned int u32;
  16. typedef unsigned long long u64;
  17. namespace fastio{
  18. #define FILE_SIZE 1<<23
  19. char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
  20. inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
  21. inline void putc(char x){(*p3++=x);}
  22. template<typename T> void read(T &x){
  23. x=0;char c=getchar();T neg=0;
  24. while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
  25. while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. if(neg) x=(~x)+1;
  27. }
  28. template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
  29. template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
  30. void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
  31. }
  32. const int MAXN=1e5;
  33. const int SQRT=316;
  34. int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
  35. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  36. vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
  37. struct node{int l,r,val,lz;} s[MAXN*4+5];
  38. void pushup(int k){
  39. if(s[k].l==s[k].r) s[k].val=!s[k].lz;
  40. else s[k].val=(s[k].lz)?0:(s[k<<1].val+s[k<<1|1].val);
  41. }
  42. void build(int k,int l,int r){
  43. s[k].l=l;s[k].r=r;if(l==r){pushup(k);return;}int mid=l+r>>1;
  44. build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
  45. }
  46. void modify(int k,int l,int r,int x){
  47. if(l<=s[k].l&&s[k].r<=r){s[k].lz+=x;pushup(k);return;}
  48. int mid=s[k].l+s[k].r>>1;
  49. if(r<=mid) modify(k<<1,l,r,x);
  50. else if(l>mid) modify(k<<1|1,l,r,x);
  51. else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
  52. pushup(k);
  53. }
  54. void dfs0(int x=1,int f=0){
  55. bgt[x]=++tim;
  56. for(int e=hd[x];e;e=nxt[e]){
  57. int y=to[e];if(y==f) continue;
  58. dfs0(y,x);
  59. } edt[x]=tim;
  60. }
  61. int ans[MAXN+5];
  62. void calc(int x=1,int f=0){
  63. for(int i=0;i<qv[x].size();i++){
  64. int y=qv[x][i];
  65. modify(1,bgt[x],edt[x],1);
  66. modify(1,bgt[y],edt[y],1);
  67. } ans[x]=n-s[1].val;
  68. if(ans[x]) ans[x]--;
  69. for(int e=hd[x];e;e=nxt[e]){
  70. int y=to[e];if(y==f) continue;
  71. calc(y,x);
  72. }
  73. for(int i=0;i<qv[x].size();i++){
  74. int y=qv[x][i];
  75. modify(1,bgt[x],edt[x],-1);
  76. modify(1,bgt[y],edt[y],-1);
  77. }
  78. }
  79. int main(){
  80. scanf("%d%d",&n,&m);
  81. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
  82. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
  83. dfs0();build(1,1,n);calc();
  84. for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
  85. return 0;
  86. }

解法三:主席树+标记永久化

qwq 事实上与解法二本质上相同?

大概就是区间赋 \(1\),求全局 \(0\) 的个数,回到历史版本,这个显然主席树可以实现。

然鹅这是蒟蒻第一次打带区间修改的主席树哦

貌似这题空间卡得蛮紧的

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define fi first
  4. #define se second
  5. #define fill0(a) memset(a,0,sizeof(a))
  6. #define fill1(a) memset(a,-1,sizeof(a))
  7. #define fillbig(a) memset(a,63,sizeof(a))
  8. #define pb push_back
  9. #define ppb pop_back
  10. #define mp make_pair
  11. template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
  12. template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
  13. typedef pair<int,int> pii;
  14. typedef long long ll;
  15. typedef unsigned int u32;
  16. typedef unsigned long long u64;
  17. namespace fastio{
  18. #define FILE_SIZE 1<<23
  19. char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
  20. inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
  21. inline void putc(char x){(*p3++=x);}
  22. template<typename T> void read(T &x){
  23. x=0;char c=getchar();T neg=0;
  24. while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
  25. while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. if(neg) x=(~x)+1;
  27. }
  28. template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
  29. template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
  30. void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
  31. }
  32. const int MAXN=1e5;
  33. const int SQRT=316;
  34. int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
  35. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  36. vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
  37. struct node{int val,lz,ch[2];} s[MAXN*120+5];
  38. void pushup(int k,int l,int r){
  39. if(l==r) s[k].val=!s[k].lz;
  40. else s[k].val=(s[k].lz)?0:(s[s[k].ch[0]].val+s[s[k].ch[1]].val);
  41. }
  42. int pre,ncnt=0;
  43. void build(int &k,int l,int r){
  44. k=++ncnt;if(l==r){pushup(k,l,r);return;}int mid=l+r>>1;
  45. build(s[k].ch[0],l,mid);build(s[k].ch[1],mid+1,r);pushup(k,l,r);
  46. }
  47. int modify(int k,int ql,int qr,int l,int r,int x){
  48. int id=++ncnt;s[id]=s[k];
  49. if(ql<=l&&r<=qr){s[id].lz+=x;pushup(id,l,r);return id;}
  50. int mid=l+r>>1;
  51. if(qr<=mid) s[id].ch[0]=modify(s[k].ch[0],ql,qr,l,mid,x);
  52. else if(ql>mid) s[id].ch[1]=modify(s[k].ch[1],ql,qr,mid+1,r,x);
  53. else{
  54. s[id].ch[0]=modify(s[k].ch[0],ql,mid,l,mid,x);
  55. s[id].ch[1]=modify(s[k].ch[1],mid+1,qr,mid+1,r,x);
  56. } pushup(id,l,r);return id;
  57. }
  58. void dfs0(int x=1,int f=0){
  59. bgt[x]=++tim;
  60. for(int e=hd[x];e;e=nxt[e]){
  61. int y=to[e];if(y==f) continue;
  62. dfs0(y,x);
  63. } edt[x]=tim;
  64. }
  65. int ans[MAXN+5];
  66. void calc(int x=1,int f=0){
  67. int tmp=pre;
  68. for(int i=0;i<qv[x].size();i++){
  69. int y=qv[x][i];
  70. pre=modify(pre,bgt[x],edt[x],1,n,1);
  71. pre=modify(pre,bgt[y],edt[y],1,n,1);
  72. } ans[x]=n-s[pre].val;
  73. if(ans[x]) ans[x]--;
  74. for(int e=hd[x];e;e=nxt[e]){
  75. int y=to[e];if(y==f) continue;
  76. calc(y,x);
  77. } pre=tmp;
  78. }
  79. int main(){
  80. scanf("%d%d",&n,&m);
  81. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
  82. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
  83. dfs0();build(pre,1,n);calc();
  84. for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
  85. return 0;
  86. }

解法四:线段树

这是我的解法,似乎 rng 神仙也是这个解法?

还是利用“\(0\) 是所有 \(cnt_u\) 的最小可到达的值”这个性质。我们知道 \(cnt_u=0\) 的个数不太好维护,那么我们怎样将其转化为可维护的东西呢?

考虑借鉴 CF997E 的套路,\(cnt_u=0\) 的个数不太好维护,可是区间最小值的个数非常好维护!也就是说,我们每个区间维护区间最小值的个数,查询的时候,如果全局最小值 \(>0\) 就说明不存在 \(cnt_u=0\),否则直接返回全局最小值的个数即可。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define fi first
  4. #define se second
  5. #define fill0(a) memset(a,0,sizeof(a))
  6. #define fill1(a) memset(a,-1,sizeof(a))
  7. #define fillbig(a) memset(a,63,sizeof(a))
  8. #define pb push_back
  9. #define ppb pop_back
  10. #define mp make_pair
  11. template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
  12. template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
  13. typedef pair<int,int> pii;
  14. typedef long long ll;
  15. typedef unsigned int u32;
  16. typedef unsigned long long u64;
  17. namespace fastio{
  18. #define FILE_SIZE 1<<23
  19. char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
  20. inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
  21. inline void putc(char x){(*p3++=x);}
  22. template<typename T> void read(T &x){
  23. x=0;char c=getchar();T neg=0;
  24. while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
  25. while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. if(neg) x=(~x)+1;
  27. }
  28. template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
  29. template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
  30. void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
  31. }
  32. const int MAXN=1e5;
  33. int n,m,ans[MAXN+5];vector<int> qv[MAXN+5];
  34. int hd[MAXN+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
  35. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  36. int bgt[MAXN+5],edt[MAXN+5],tim=0;
  37. void dfs(int x,int f){
  38. bgt[x]=++tim;
  39. for(int e=hd[x];e;e=nxt[e]){
  40. int y=to[e];if(y==f) continue;
  41. dfs(y,x);
  42. } edt[x]=tim;
  43. }
  44. struct node{int l,r,mn,mnc,lz;} s[MAXN*4+5];
  45. void pushup(int k){
  46. s[k].mn=min(s[k<<1].mn,s[k<<1|1].mn);
  47. s[k].mnc=0;
  48. if(s[k].mn==s[k<<1].mn) s[k].mnc+=s[k<<1].mnc;
  49. if(s[k].mn==s[k<<1|1].mn) s[k].mnc+=s[k<<1|1].mnc;
  50. }
  51. void build(int k,int l,int r){
  52. s[k].l=l;s[k].r=r;s[k].mnc=r-l+1;if(l==r) return;
  53. int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
  54. }
  55. void pushdown(int k){
  56. if(s[k].lz){
  57. s[k<<1].mn+=s[k].lz;s[k<<1].lz+=s[k].lz;
  58. s[k<<1|1].mn+=s[k].lz;s[k<<1|1].lz+=s[k].lz;
  59. s[k].lz=0;
  60. }
  61. }
  62. void modify(int k,int l,int r,int x){
  63. if(l<=s[k].l&&s[k].r<=r){
  64. s[k].mn+=x;s[k].lz+=x;return;
  65. } pushdown(k);int mid=s[k].l+s[k].r>>1;
  66. if(r<=mid) modify(k<<1,l,r,x);
  67. else if(l>mid) modify(k<<1|1,l,r,x);
  68. else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
  69. pushup(k);
  70. }
  71. void calc(int x,int f){
  72. for(int i=0;i<qv[x].size();i++){
  73. int y=qv[x][i];
  74. modify(1,bgt[x],edt[x],1);
  75. modify(1,bgt[y],edt[y],1);
  76. } if(s[1].mn>0) ans[x]=n;
  77. else ans[x]=n-s[1].mnc;
  78. if(ans[x]) ans[x]--;
  79. for(int e=hd[x];e;e=nxt[e]){
  80. int y=to[e];if(y==f) continue;
  81. calc(y,x);
  82. }
  83. for(int i=0;i<qv[x].size();i++){
  84. int y=qv[x][i];
  85. modify(1,bgt[x],edt[x],-1);
  86. modify(1,bgt[y],edt[y],-1);
  87. }
  88. }
  89. int main(){
  90. scanf("%d%d",&n,&m);build(1,1,n);
  91. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);dfs(1,0);
  92. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);calc(1,0);
  93. for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
  94. return 0;
  95. }

解法五:可撤销线段树

这是 ycx 发明出来的解法,ycx 带发明家!

不难发现这个撤销操作具有时效性,也就是说,我们访问完 \(x\) 子树内所有节点之后,下一步即将撤销的操作就是 \(x\) 加入的操作,故我们可以借鉴线段树分治的思想,用个记录这一步的修改的节点编号以及它们原来的值,回溯的时候就按照线段树分治的套路撤销即可。

时空复杂度均为 \(n\log n\),貌似这个解法是同时卡着时限和空限过去的?(3960ms/4000ms,225400KB/262144KB)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define fi first
  4. #define se second
  5. #define fill0(a) memset(a,0,sizeof(a))
  6. #define fill1(a) memset(a,-1,sizeof(a))
  7. #define fillbig(a) memset(a,63,sizeof(a))
  8. #define pb push_back
  9. #define ppb pop_back
  10. #define mp make_pair
  11. template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
  12. template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
  13. typedef pair<int,int> pii;
  14. typedef long long ll;
  15. typedef unsigned int u32;
  16. typedef unsigned long long u64;
  17. namespace fastio{
  18. #define FILE_SIZE 1<<23
  19. char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
  20. inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
  21. inline void putc(char x){(*p3++=x);}
  22. template<typename T> void read(T &x){
  23. x=0;char c=getchar();T neg=0;
  24. while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
  25. while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. if(neg) x=(~x)+1;
  27. }
  28. template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
  29. template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
  30. void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
  31. }
  32. const int MAXN=1e5;
  33. int n,m,ans[MAXN+5];vector<int> qv[MAXN+5];
  34. int hd[MAXN+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
  35. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  36. int bgt[MAXN+5],edt[MAXN+5],tim=0;
  37. void dfs(int x,int f){
  38. bgt[x]=++tim;
  39. for(int e=hd[x];e;e=nxt[e]){
  40. int y=to[e];if(y==f) continue;
  41. dfs(y,x);
  42. } edt[x]=tim;
  43. }
  44. struct node{int val,lz;} s[MAXN*4+5];
  45. stack<stack<pair<int,node> > > stk;
  46. void pushup(int k){
  47. stk.top().push(mp(k,s[k]));
  48. s[k].val=s[k<<1].val+s[k<<1|1].val;
  49. }
  50. void build(int k,int l,int r){
  51. if(k==1) stk.push(stack<pair<int,node> >());
  52. s[k].val=r-l+1;if(l==r) return;
  53. int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
  54. pushup(k);
  55. }
  56. void tag(int k){stk.top().push(mp(k,s[k]));s[k].val=0;s[k].lz=1;}
  57. void pushdown(int k){
  58. stk.top().push(mp(k,s[k]));
  59. if(s[k].lz) tag(k<<1),tag(k<<1|1),s[k].lz=0;
  60. }
  61. void modify(int k,int ql,int qr,int l,int r){
  62. if(k==1) stk.push(stack<pair<int,node> >());
  63. if(ql<=l&&r<=qr){tag(k);return;}
  64. pushdown(k);int mid=l+r>>1;
  65. if(qr<=mid) modify(k<<1,ql,qr,l,mid);
  66. else if(ql>mid) modify(k<<1|1,ql,qr,mid+1,r);
  67. else modify(k<<1,ql,mid,l,mid),modify(k<<1|1,mid+1,qr,mid+1,r);
  68. pushup(k);
  69. }
  70. void cancel(){
  71. stack<pair<int,node> > tmp=stk.top();stk.pop();
  72. while(!tmp.empty()) s[tmp.top().fi]=tmp.top().se,tmp.pop();
  73. }
  74. void calc(int x,int f){
  75. int tmp=stk.size();
  76. for(int i=0;i<qv[x].size();i++){
  77. int y=qv[x][i];
  78. modify(1,bgt[x],edt[x],1,n);
  79. modify(1,bgt[y],edt[y],1,n);
  80. } ans[x]=n-s[1].val;
  81. if(ans[x]) ans[x]--;
  82. for(int e=hd[x];e;e=nxt[e]){
  83. int y=to[e];if(y==f) continue;
  84. calc(y,x);
  85. }
  86. while(stk.size()>tmp) cancel();
  87. }
  88. int main(){
  89. scanf("%d%d",&n,&m);build(1,1,n);
  90. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);dfs(1,0);
  91. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);calc(1,0);
  92. for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
  93. return 0;
  94. }

综上,此题确实是一道不错的锻炼数据结构能力的 hot tea。

Codeforces 258E - Little Elephant and Tree(根号暴力/线段树+标记永久化/主席树+标记永久化/普通线段树/可撤销线段树,hot tea)的更多相关文章

  1. CodeForces - 204C Little Elephant and Furik and Rubik

    CodeForces - 204C Little Elephant and Furik and Rubik 个人感觉是很好的一道题 这道题乍一看我们无从下手,那我们就先想想怎么打暴力 暴力还不简单?枚 ...

  2. codeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(启发式合并)

    codeforces 741D Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths 题意 给出一棵树,每条边上有一个字符,字符集大小只 ...

  3. codeforces 812E Sagheer and Apple Tree(思维、nim博弈)

    codeforces 812E Sagheer and Apple Tree 题意 一棵带点权有根树,保证所有叶子节点到根的距离同奇偶. 每次可以选择一个点,把它的点权删除x,它的某个儿子的点权增加x ...

  4. codeforces 220 C. Game on Tree

    题目链接 codeforces 220 C. Game on Tree 题解 对于 1节点一定要选的 发现对于每个节点,被覆盖切选中其节点的概率为祖先个数分之一,也就是深度分之一 代码 #includ ...

  5. Codeforces D. Little Elephant and Interval(思维找规律数位dp)

    题目描述: Little Elephant and Interval time limit per test 2 seconds memory limit per test 256 megabytes ...

  6. Codeforces E. Alyona and a tree(二分树上差分)

    题目描述: Alyona and a tree time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  7. CodeForces - 963B Destruction of a Tree (dfs+思维题)

    B. Destruction of a Tree time limit per test 1 second memory limit per test 256 megabytes input stan ...

  8. Codeforces 1039D You Are Given a Tree [根号分治,整体二分,贪心]

    洛谷 Codeforces 根号分治真是妙啊. 思路 考虑对于单独的一个\(k\)如何计算答案. 与"赛道修建"非常相似,但那题要求边,这题要求点,所以更加简单. 在每一个点贪心地 ...

  9. CF1039D You Are Given a Tree 根号分治,贪心

    CF1039D You Are Given a Tree LG传送门 根号分治好题. 这题可以整体二分,但我太菜了,不会. 根号分治怎么考虑呢?先想想\(n^2\)暴力吧.对于每一个要求的\(k\), ...

随机推荐

  1. python常用功能

    1. 获取昨天日期 引入datetime模块 import datetime def getYesterday(): today = datetime.date.today() #返回当前本地日期 # ...

  2. Less-(1~4) union select

    Less-1: 核心语句: 无任何防护:回显查询结果或错误内容. 输入单引号闭合语句中的单引号,#注释后面的内容,即可注入.由于有查询结果回显,直接联合注入即可. 1'order by x #(有些环 ...

  3. 【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)

    定义 能够在key插入时一直保持平衡的二叉查找树: AVL树 利用AVL树实现ADT Map, 基本上与BST的实现相同,不同之处仅在于二叉树的生成与维护过程 平衡因子 AVL树的实现中, 需要对每个 ...

  4. Java版人脸检测详解下篇:编码

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. OO_JAVA_JML系列第三次作业__架构之谈

    OO_JAVA_JML系列第三次作业 ## ----架构之谈 目录 OO_JAVA_JML系列第三次作业 出发点 操作的可分离性 操作本身的多样性 实现手段:表驱动编程 储存 注册 出发点 操作的可分 ...

  6. 洛谷 P4774 [NOI2018] 屠龙勇士

    链接:P4774 前言: 交了18遍最后发现是多组数据没清空/ll 题意: 其实就是个扩中. 分析过程: 首先发现根据题目描述的选择剑的方式,每条龙对应的剑都是固定的,有查询前驱,后继(在该数不存在前 ...

  7. 【http】https加速优化

    目录 前言 HTTPS 的连接很慢 https 步骤简要划分 握手耗时 证书验证 CRL OCSP 硬件优化 软件优化 软件升级 协议优化 证书优化 会话复用 会话票证 预共享密钥 前言 主要记录 h ...

  8. Oracle ORA 12541 报错解决过程

    Oracle 导入全库之后使用plsql登陆时报错 版本12C版本2 ORA-12541: TNS: No Listener 再oracle主机本地可以使用sqlplus 登陆,但是使用plsql无法 ...

  9. GO 字符串反转

    字符串反转 即 abc 反转后成 cba 思路:两边都设置一个游标,然后互换位置,游标同步向中间移动,再互换. for i, j := 0, len(s)-1; i < j; i, j = i+ ...

  10. PTA 7-1 还原二叉树 (25分)

    PTA 7-1 还原二叉树 (25分) 给定一棵二叉树的先序遍历序列和中序遍历序列,要求计算该二叉树的高度. 输入格式: 输入首先给出正整数N(≤50),为树中结点总数.下面两行先后给出先序和中序遍历 ...