Codeforces 258E - Little Elephant and Tree(根号暴力/线段树+标记永久化/主席树+标记永久化/普通线段树/可撤销线段树,hot tea)
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 也能过?
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int SQRT=316;
int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
int blk,blk_cnt,L[SQRT+5],R[SQRT+5],bel[MAXN+5];
int val[MAXN+5],tag[SQRT+5];short cnt[SQRT+5][MAXN+5];
void dfs0(int x=1,int f=0){
bgt[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs0(y,x);
} edt[x]=tim;
}
void add(int l,int r,int v){
if(bel[l]==bel[r]){
for(int i=l;i<=r;i++) cnt[bel[l]][val[i]]--;
for(int i=l;i<=r;i++) val[i]+=v;
for(int i=l;i<=r;i++) cnt[bel[l]][val[i]]++;
} else {
for(int i=l;i<=R[bel[l]];i++) cnt[bel[l]][val[i]]--;
for(int i=l;i<=R[bel[l]];i++) val[i]+=v;
for(int i=l;i<=R[bel[l]];i++) cnt[bel[l]][val[i]]++;
for(int i=L[bel[r]];i<=r;i++) cnt[bel[r]][val[i]]--;
for(int i=L[bel[r]];i<=r;i++) val[i]+=v;
for(int i=L[bel[r]];i<=r;i++) cnt[bel[r]][val[i]]++;
for(int i=bel[l]+1;i<=bel[r]-1;i++) tag[i]+=v;
}
}
int query(){
int ret=0;
for(int i=1;i<=blk_cnt;i++) ret+=cnt[i][-tag[i]];
return n-ret;
}
int ans[MAXN+5];
void calc(int x=1,int f=0){
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
add(bgt[x],edt[x],1);
add(bgt[y],edt[y],1);
} ans[x]=query();
if(ans[x]) ans[x]--;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
calc(y,x);
}
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
add(bgt[x],edt[x],-1);
add(bgt[y],edt[y],-1);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
blk=(int)pow(n,0.5);blk_cnt=(n-1)/blk+1;
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk+1;R[i]=min(i*blk,n);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
cnt[i][0]=R[i]-L[i]+1;
} dfs0();calc();
for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
return 0;
}
解法二:线段树+标记永久化
u1s1 在做此题之前我还不知道啥是标记永久化,感谢这题让我学会了一个新算法
在上面的根号暴力中,我们没有用到此题一个很重要的性质,那就是我们肯定是先加入再撤销贡献的,也就是说任何时刻都有 \(\forall u,cnt_u\ge 0\)。
于是我们考虑这样一个思路,用线段树维护上面的操作。当我们执行线段树区间修改的时候,我们标记照样打,不过我们不下放标记(这是标记永久化与普通线段树的区别所在)。显然根据之前的推论任意时刻任意节点的标记都非负。线段树上每个节点维护一个值 \(val\) 表示该节点表示的区间中有多少个 \(cnt_u=0\),而如果我们发现某个区间的标记非负,就意味着该区间中所有 \(cnt_u\) 都 \(>0\),直接 \(val_i=0\) 即可。否则 \(val_i\) 就是其左右儿子 \(val\) 之和。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int SQRT=316;
int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
struct node{int l,r,val,lz;} s[MAXN*4+5];
void pushup(int k){
if(s[k].l==s[k].r) s[k].val=!s[k].lz;
else s[k].val=(s[k].lz)?0:(s[k<<1].val+s[k<<1|1].val);
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r){pushup(k);return;}int mid=l+r>>1;
build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){s[k].lz+=x;pushup(k);return;}
int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
pushup(k);
}
void dfs0(int x=1,int f=0){
bgt[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs0(y,x);
} edt[x]=tim;
}
int ans[MAXN+5];
void calc(int x=1,int f=0){
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
modify(1,bgt[x],edt[x],1);
modify(1,bgt[y],edt[y],1);
} ans[x]=n-s[1].val;
if(ans[x]) ans[x]--;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
calc(y,x);
}
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
modify(1,bgt[x],edt[x],-1);
modify(1,bgt[y],edt[y],-1);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
dfs0();build(1,1,n);calc();
for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
return 0;
}
解法三:主席树+标记永久化
qwq 事实上与解法二本质上相同?
大概就是区间赋 \(1\),求全局 \(0\) 的个数,回到历史版本,这个显然主席树可以实现。
然鹅这是蒟蒻第一次打带区间修改的主席树哦
貌似这题空间卡得蛮紧的
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int SQRT=316;
int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
vector<int> qv[MAXN+5];int bgt[MAXN+5],edt[MAXN+5],tim=0;
struct node{int val,lz,ch[2];} s[MAXN*120+5];
void pushup(int k,int l,int r){
if(l==r) s[k].val=!s[k].lz;
else s[k].val=(s[k].lz)?0:(s[s[k].ch[0]].val+s[s[k].ch[1]].val);
}
int pre,ncnt=0;
void build(int &k,int l,int r){
k=++ncnt;if(l==r){pushup(k,l,r);return;}int mid=l+r>>1;
build(s[k].ch[0],l,mid);build(s[k].ch[1],mid+1,r);pushup(k,l,r);
}
int modify(int k,int ql,int qr,int l,int r,int x){
int id=++ncnt;s[id]=s[k];
if(ql<=l&&r<=qr){s[id].lz+=x;pushup(id,l,r);return id;}
int mid=l+r>>1;
if(qr<=mid) s[id].ch[0]=modify(s[k].ch[0],ql,qr,l,mid,x);
else if(ql>mid) s[id].ch[1]=modify(s[k].ch[1],ql,qr,mid+1,r,x);
else{
s[id].ch[0]=modify(s[k].ch[0],ql,mid,l,mid,x);
s[id].ch[1]=modify(s[k].ch[1],mid+1,qr,mid+1,r,x);
} pushup(id,l,r);return id;
}
void dfs0(int x=1,int f=0){
bgt[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs0(y,x);
} edt[x]=tim;
}
int ans[MAXN+5];
void calc(int x=1,int f=0){
int tmp=pre;
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
pre=modify(pre,bgt[x],edt[x],1,n,1);
pre=modify(pre,bgt[y],edt[y],1,n,1);
} ans[x]=n-s[pre].val;
if(ans[x]) ans[x]--;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
calc(y,x);
} pre=tmp;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),qv[u].pb(v),qv[v].pb(u);
dfs0();build(pre,1,n);calc();
for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
return 0;
}
解法四:线段树
这是我的解法,似乎 rng 神仙也是这个解法?
还是利用“\(0\) 是所有 \(cnt_u\) 的最小可到达的值”这个性质。我们知道 \(cnt_u=0\) 的个数不太好维护,那么我们怎样将其转化为可维护的东西呢?
考虑借鉴 CF997E 的套路,\(cnt_u=0\) 的个数不太好维护,可是区间最小值的个数非常好维护!也就是说,我们每个区间维护区间最小值的个数,查询的时候,如果全局最小值 \(>0\) 就说明不存在 \(cnt_u=0\),否则直接返回全局最小值的个数即可。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
int n,m,ans[MAXN+5];vector<int> qv[MAXN+5];
int hd[MAXN+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int bgt[MAXN+5],edt[MAXN+5],tim=0;
void dfs(int x,int f){
bgt[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,x);
} edt[x]=tim;
}
struct node{int l,r,mn,mnc,lz;} s[MAXN*4+5];
void pushup(int k){
s[k].mn=min(s[k<<1].mn,s[k<<1|1].mn);
s[k].mnc=0;
if(s[k].mn==s[k<<1].mn) s[k].mnc+=s[k<<1].mnc;
if(s[k].mn==s[k<<1|1].mn) s[k].mnc+=s[k<<1|1].mnc;
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].mnc=r-l+1;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(s[k].lz){
s[k<<1].mn+=s[k].lz;s[k<<1].lz+=s[k].lz;
s[k<<1|1].mn+=s[k].lz;s[k<<1|1].lz+=s[k].lz;
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){
s[k].mn+=x;s[k].lz+=x;return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
pushup(k);
}
void calc(int x,int f){
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
modify(1,bgt[x],edt[x],1);
modify(1,bgt[y],edt[y],1);
} if(s[1].mn>0) ans[x]=n;
else ans[x]=n-s[1].mnc;
if(ans[x]) ans[x]--;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
calc(y,x);
}
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
modify(1,bgt[x],edt[x],-1);
modify(1,bgt[y],edt[y],-1);
}
}
int main(){
scanf("%d%d",&n,&m);build(1,1,n);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);dfs(1,0);
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);
for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
return 0;
}
解法五:可撤销线段树
这是 ycx 发明出来的解法,ycx 带发明家!
不难发现这个撤销操作具有时效性,也就是说,我们访问完 \(x\) 子树内所有节点之后,下一步即将撤销的操作就是 \(x\) 加入的操作,故我们可以借鉴线段树分治的思想,用个栈记录这一步的修改的节点编号以及它们原来的值,回溯的时候就按照线段树分治的套路撤销即可。
时空复杂度均为 \(n\log n\),貌似这个解法是同时卡着时限和空限过去的?(3960ms/4000ms,225400KB/262144KB)
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
int n,m,ans[MAXN+5];vector<int> qv[MAXN+5];
int hd[MAXN+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int bgt[MAXN+5],edt[MAXN+5],tim=0;
void dfs(int x,int f){
bgt[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,x);
} edt[x]=tim;
}
struct node{int val,lz;} s[MAXN*4+5];
stack<stack<pair<int,node> > > stk;
void pushup(int k){
stk.top().push(mp(k,s[k]));
s[k].val=s[k<<1].val+s[k<<1|1].val;
}
void build(int k,int l,int r){
if(k==1) stk.push(stack<pair<int,node> >());
s[k].val=r-l+1;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
pushup(k);
}
void tag(int k){stk.top().push(mp(k,s[k]));s[k].val=0;s[k].lz=1;}
void pushdown(int k){
stk.top().push(mp(k,s[k]));
if(s[k].lz) tag(k<<1),tag(k<<1|1),s[k].lz=0;
}
void modify(int k,int ql,int qr,int l,int r){
if(k==1) stk.push(stack<pair<int,node> >());
if(ql<=l&&r<=qr){tag(k);return;}
pushdown(k);int mid=l+r>>1;
if(qr<=mid) modify(k<<1,ql,qr,l,mid);
else if(ql>mid) modify(k<<1|1,ql,qr,mid+1,r);
else modify(k<<1,ql,mid,l,mid),modify(k<<1|1,mid+1,qr,mid+1,r);
pushup(k);
}
void cancel(){
stack<pair<int,node> > tmp=stk.top();stk.pop();
while(!tmp.empty()) s[tmp.top().fi]=tmp.top().se,tmp.pop();
}
void calc(int x,int f){
int tmp=stk.size();
for(int i=0;i<qv[x].size();i++){
int y=qv[x][i];
modify(1,bgt[x],edt[x],1,n);
modify(1,bgt[y],edt[y],1,n);
} ans[x]=n-s[1].val;
if(ans[x]) ans[x]--;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
calc(y,x);
}
while(stk.size()>tmp) cancel();
}
int main(){
scanf("%d%d",&n,&m);build(1,1,n);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);dfs(1,0);
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);
for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
return 0;
}
综上,此题确实是一道不错的锻炼数据结构能力的 hot tea。
Codeforces 258E - Little Elephant and Tree(根号暴力/线段树+标记永久化/主席树+标记永久化/普通线段树/可撤销线段树,hot tea)的更多相关文章
- CodeForces - 204C Little Elephant and Furik and Rubik
CodeForces - 204C Little Elephant and Furik and Rubik 个人感觉是很好的一道题 这道题乍一看我们无从下手,那我们就先想想怎么打暴力 暴力还不简单?枚 ...
- 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 题意 给出一棵树,每条边上有一个字符,字符集大小只 ...
- codeforces 812E Sagheer and Apple Tree(思维、nim博弈)
codeforces 812E Sagheer and Apple Tree 题意 一棵带点权有根树,保证所有叶子节点到根的距离同奇偶. 每次可以选择一个点,把它的点权删除x,它的某个儿子的点权增加x ...
- codeforces 220 C. Game on Tree
题目链接 codeforces 220 C. Game on Tree 题解 对于 1节点一定要选的 发现对于每个节点,被覆盖切选中其节点的概率为祖先个数分之一,也就是深度分之一 代码 #includ ...
- Codeforces D. Little Elephant and Interval(思维找规律数位dp)
题目描述: Little Elephant and Interval time limit per test 2 seconds memory limit per test 256 megabytes ...
- Codeforces E. Alyona and a tree(二分树上差分)
题目描述: Alyona and a tree time limit per test 2 seconds memory limit per test 256 megabytes input stan ...
- 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 ...
- Codeforces 1039D You Are Given a Tree [根号分治,整体二分,贪心]
洛谷 Codeforces 根号分治真是妙啊. 思路 考虑对于单独的一个\(k\)如何计算答案. 与"赛道修建"非常相似,但那题要求边,这题要求点,所以更加简单. 在每一个点贪心地 ...
- CF1039D You Are Given a Tree 根号分治,贪心
CF1039D You Are Given a Tree LG传送门 根号分治好题. 这题可以整体二分,但我太菜了,不会. 根号分治怎么考虑呢?先想想\(n^2\)暴力吧.对于每一个要求的\(k\), ...
随机推荐
- 如何将jdk12的源码导入idea
如何将jdk12的源码导入idea中 一 首先,在idea中新建一个java工程 接着,在本地找到jdk所在的文件目录,进入jdk目录,找到javasrc目录或者一个src.zip的压缩包, 在向下或 ...
- Java只有值传递
二哥,好久没更新面试官系列的文章了啊,真的是把我等着急了,所以特意过来催催.我最近一段时间在找工作,能从二哥的文章中学到一点就多一点信心啊! 说句实在话,离读者 trust you 发给我这段信息已经 ...
- 第三次Alpha Scrum Meeting
本次会议为Alpha阶段第三次Scrum Meeting会议 会议概要 会议时间:2021年4月26日 会议地点:线上会议 会议时长:20min 会议内容简介:本次会议主要由每个人展示自己目前完成的工 ...
- [软工作业]-软件案例分析-CSDN
[软工作业]-软件案例分析-CSDN(app) 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人博客作业-软件案例分析 我在这个课程的目标是 ...
- mongodb的聚合操作
在mongodb中有时候我们需要对数据进行分析操作,比如一些统计操作,这个时候简单的查询操作(find)就搞不定这些需求,因此就需要使用 聚合框架(aggregation) 来完成.在mongodb ...
- webpack基础以及webpack中babel的配置
webpack 安装 npm 初始化,控制台输入 npm init -y webpack 安装 npm i webpack webpack-cli -D 新建 webpack.config.js co ...
- sql 多表联合查询更新
sqlserver: update A a set a.i = b.k from B b where a.key = b.key oracle : update A a set a.i = (sele ...
- 像素设定 牛客网 程序员面试金典 C++ Python
像素设定 牛客网 程序员面试金典 题目描述 有一个单色屏幕储存在一维数组中,其中数组的每个元素代表连续的8位的像素的值,请实现一个函数,将第x到第y个像素涂上颜色(像素标号从零开始),并尝试尽量使用最 ...
- filter tools
// 过滤商品分类 Vue.filter("cateFilter", (data) => { let tmp = ["一级分类", "二级分 ...
- SSH 提示密码过期,如何通过 ansible 批量更新线上服务器密码
起因 线上环境是在内网,登陆线上环境需要使用 VPN + 堡垒机 登陆,但是我日常登陆线上环境都是 VPN + 堡垒机 + Socks5常驻代理,在shell端只需要保存会话,会话使用socks5代理 ...