虚树总结&题单&简要题解
简介
虚树,即剔除所有无关结点,只保留询问点和询问点的相关结点(两两之间的LCA),建一棵新树,这棵新树就是虚树。通过虚树,可以有效的减小询问(甚至修改)的复杂度。设询问点的个数是\(k\),那么建虚树的一般方法的时间复杂度为\(O(k \log k)\)。
构建方法
把所有询问点按dfs序排个序。
求出所有相邻结点的LCA(相关点)加入数组,结束后把根结点(\(1\))也加入数组。
再把所有询问点和相关点按dfs序排个序。
用栈维护虚树上根结点出发的一条链,按dfs序逐个插入结点,弹栈时连虚树边。
逐个弹出栈中剩余结点,并同时连虚树边。
代码
void build_itree(){
rin(i,1,cnt) arr[++len]=uu[i],arr[++len]=vv[i];
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
int temp=len;
rin(i,1,temp-1) arr[++len]=lca(arr[i],arr[i+1]);
arr[++len]=1;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
rin(i,1,len) id2[arr[i]]=i;
top=0;
rin(i,1,len){
ini[arr[i]]=true;
while(top&&id[sta[top]]+siz[sta[top]]-1<id[arr[i]]) add_iedge(id2[sta[top-1]],id2[sta[top]]),top--;
sta[++top]=arr[i];
}
while(top>1) add_iedge(id2[sta[top-1]],id2[sta[top]]),top--;
}
[BZOJ2286][SDOI2011]消耗战
分析
模板题。对于每个询问,建出虚树后在虚树上跑树形DP(当时并不会建虚树所以直接抄的题解,建虚树的方法可能和上面讲的不太一样)即可。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=250005;
int n,m,k,ecnt,head[MAXN];
int fa[MAXN],dep[MAXN],siz[MAXN],pc[MAXN],_top[MAXN],id[MAXN],tot;
int h[MAXN<<1],sta[MAXN],top;
LL f[MAXN],minw[MAXN];
std::vector<int> vec[MAXN];
struct Edge{
int to,nxt,w;
}e[MAXN<<1];
inline void add_edge(int bg,int ed,int val){
++ecnt;
e[ecnt].nxt=head[bg];
e[ecnt].to=ed;
e[ecnt].w=val;
head[bg]=ecnt;
}
void dfs1(int x,int pre,int depth){
fa[x]=pre;
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
if(ver==pre) continue;
minw[ver]=std::min(minw[x],1ll*e[i].w);
dfs1(ver,x,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
}
void dfs2(int x,int topf){
_top[x]=topf;
id[x]=++tot;
if(!pc[x]) return;
dfs2(pc[x],topf);
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]||ver==pc[x]) continue;
dfs2(ver,ver);
}
}
inline int lca(int x,int y){
while(_top[x]!=_top[y]){
if(dep[_top[x]]<dep[_top[y]]) std::swap(x,y);
x=fa[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline bool cmp(int x,int y){
return id[x]<id[y];
}
inline void add_iedge(int bg,int ed){
vec[bg].push_back(ed);
}
void ins(int x){
if(top==1){
sta[++top]=x;
return;
}
int _lca=lca(x,sta[top]);
if(_lca==sta[top]) return;
while(top>1&&id[sta[top-1]]>=id[_lca]) add_iedge(sta[top-1],sta[top]),top--;
if(_lca!=sta[top]) add_iedge(_lca,sta[top]),sta[top]=_lca;
sta[++top]=x;
}
void dfs3(int x){
if(!vec[x].size()){
f[x]=minw[x];
return;
}
f[x]=0;
rin(i,0,(int)vec[x].size()-1){
int ver=vec[x][i];
dfs3(ver);
f[x]+=std::min(f[ver],minw[ver]);
}
vec[x].clear();
}
int main(){
n=read();
rin(i,2,n){
int u=read(),v=read(),w=read();
add_edge(u,v,w);
add_edge(v,u,w);
}
minw[1]=1e18;
dfs1(1,0,1);
dfs2(1,1);
m=read();
while(m--){
k=read();
rin(i,1,k) h[i]=read();
std::sort(h+1,h+k+1,cmp);
top=1,sta[1]=1;
rin(i,1,k) ins(h[i]);
while(top>1) add_iedge(sta[top-1],sta[top]),top--;
dfs3(1);
printf("%lld\n",f[1]);
}
return 0;
}
[CF1073G]Yet Another LCP Problem
分析
说白了就是给SAM的parent树建虚树。
众所周知,parent树是反串的后缀树。将字符串reverse()
,建出后缀树。于是我们所求的问题转化为了给你两个点集,要求所有两个结点分别来自不同点集的点对的LCA的深度之和。
这个问题似乎可以用[BZOJ3626][LNOI2014]LCA的方法解决,不过我们还可以做到更快。考虑虚树上每条边的贡献,显然(?)是\(cnta[x] \times cntb[x] \times w[x]\),即子树内集合\(A\)的点的个数乘集合\(B\)的点的个数再乘这条边的边权(后缀树上这个转移的串长)。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=200005;
int n,q,las,tot,ecnt,head[MAXN<<1];
int fa[MAXN<<1],dep[MAXN<<1],siz[MAXN<<1],pc[MAXN<<1],_top[MAXN<<1],id[MAXN<<1],totid;
int seta[MAXN<<1],setb[MAXN<<1],siza,sizb,arr[MAXN<<2],len,sta[MAXN<<1],top;
int frm[MAXN<<1],cnta[MAXN<<1],cntb[MAXN<<1];
char s[MAXN];
std::vector<int> vec[MAXN<<1];
struct sam{
int fa,to[26];
int len;
}a[MAXN<<1];
struct Edge{
int to,nxt;
}e[MAXN<<1];
inline void add_edge(int bg,int ed){
ecnt++;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
}
inline void add_iedge(int bg,int ed){
vec[bg].push_back(ed);
}
void extend(int c,int np){
int p=las;las=np,a[np].len=a[p].len+1;
while(p&&!a[p].to[c]) a[p].to[c]=np,p=a[p].fa;
if(!p){a[np].fa=1;return;}
int q=a[p].to[c];
if(a[p].len+1==a[q].len){a[np].fa=q;return;}
int nq=++tot;a[nq]=a[q],a[nq].len=a[p].len+1,a[np].fa=a[q].fa=nq;
while(p&&a[p].to[c]==q) a[p].to[c]=nq,p=a[p].fa;
}
void dfs1(int x,int pre,int depth){
fa[x]=pre;
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
dfs1(ver,x,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
}
void dfs2(int x,int topf){
_top[x]=topf;
id[x]=++totid;
if(!pc[x]) return;
dfs2(pc[x],topf);
trav(i,x){
int ver=e[i].to;
if(ver==pc[x]) continue;
dfs2(ver,ver);
}
}
inline int lca(int x,int y){
while(_top[x]!=_top[y]){
if(dep[_top[x]]<dep[_top[y]]) std::swap(x,y);
x=fa[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline bool cmp(int x,int y){
return id[x]<id[y];
}
int main(){
n=read(),q=read(),las=1,tot=n+1;
scanf("%s",s+1);
irin(i,n,1) extend(s[i]-'a',i+1);
rin(i,2,tot) add_edge(a[i].fa,i);
dfs1(1,0,1);
dfs2(1,1);
while(q--){
LL ans=0;
siza=read(),sizb=read(),len=0;
rin(i,1,siza) arr[++len]=seta[i]=read()+1,cnta[seta[i]]=1;
rin(i,1,sizb) arr[++len]=setb[i]=read()+1,cntb[setb[i]]=1;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
int temp=len;
rin(i,1,temp-1) arr[++len]=lca(arr[i],arr[i+1]);
arr[++len]=1;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
top=0;
rin(i,1,len){
while(top&&id[sta[top]]+siz[sta[top]]-1<id[arr[i]]) add_iedge(sta[top-1],sta[top]),frm[sta[top]]=a[sta[top]].len-a[sta[top-1]].len,top--;
sta[++top]=arr[i];
}
rin(i,1,top-1) add_iedge(sta[i],sta[i+1]),frm[sta[i+1]]=a[sta[i+1]].len-a[sta[i]].len;
irin(i,len,1){
int x=arr[i];
rin(j,0,(int)vec[x].size()-1){
int ver=vec[x][j];
cnta[x]+=cnta[ver];
cntb[x]+=cntb[ver];
}
ans+=1ll*cnta[x]*cntb[x]*frm[x];
}
rin(i,1,len) cnta[arr[i]]=cntb[arr[i]]=0,vec[arr[i]].clear();
printf("%I64d\n",ans);
}
return 0;
}
[BZOJ5287][HNOI2018]毒瘤
分析
这题是真毒瘤,敲了2h+。
观察数据范围发现边数并不比点数多很多,建出一棵生成树后,最多只会余下\(11\)条边。所以可以先随便求出原图的一棵生成树,把多出来的边的相关结点揪出来搞一棵虚树,在虚树上的转移提前算好系数\(k[x][0/1][0/1]\),表示结点\(x\)选或不选对其虚树上的父亲选或不选的贡献的系数,最后枚举每条多出来的边的两端点的选取情况(可以通过强制其中一个端点选或不选来实现),做树形DP即可。
举例,考虑如何求\(k[x][0/1][0]\)(即不选结点\(x\)的贡献),容易想到虚树上的一条边对应原树上的一条链。令\(f[x][0]=1,f[x][1]=0\),这样可以确保算的只是不选结点\(x\)的贡献,单独把这条链拿出来做树形DP就好了,记得过程中算上分枝的贡献。求\(k[x][0/1][1]\)的过程类似。
实现复杂,代码超长,细节超多,调试困难,为出题人点赞。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
struct Erkko_istream{
inline char getc(){
static const int IN_LEN=1<<18|1;
static char buf[IN_LEN],*s,*t;
return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
}
template<typename _Tp> inline Erkko_istream & operator >> (_Tp &x){
static char c11,boo;
for(c11=getc(),boo=0;!isdigit(c11);c11=getc()){
if(c11==-1) return *this;
boo|=c11=='-';
}
for(x=0;isdigit(c11);c11=getc()) x=x*10+(c11^'0');
boo&&(x=-x);
return *this;
}
}io;
const int MAXN=100005;
const LL MOD=998244353;
int n,m,ecnt,head[MAXN],uu[15],vv[15],cnt;
int fa[MAXN],dep[MAXN],siz[MAXN],pc[MAXN],_top[MAXN],id[MAXN],tot;
int arr[45],len,sta[45],top,id2[MAXN],must[45];
LL f[MAXN][2],k[45][2][2],ans;
bool vis1[MAXN],vis2[MAXN],ini[MAXN];
std::vector<int> vec[45];
struct Edge{
int to,nxt;
}e[(MAXN+10)<<1];
inline void add_edge(int bg,int ed){
ecnt++;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
}
inline void add_iedge(int bg,int ed){
vec[bg].push_back(ed);
}
void dfs1(int x,int pre,int depth){
fa[x]=pre;
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
if(ver==pre) continue;
if(siz[ver]){
if(x>ver) continue;
++cnt;
uu[cnt]=x;
vv[cnt]=ver;
continue;
}
dfs1(ver,x,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
}
void dfs2(int x,int topf){
_top[x]=topf;
id[x]=++tot;
if(!pc[x]) return;
dfs2(pc[x],topf);
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]||ver==pc[x]) continue;
if(fa[ver]!=x) continue;
dfs2(ver,ver);
}
}
inline int lca(int x,int y){
while(_top[x]!=_top[y]){
if(dep[_top[x]]<dep[_top[y]]) std::swap(x,y);
x=fa[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline bool cmp(int x,int y){
return id[x]<id[y];
}
void build_itree(){
rin(i,1,cnt) arr[++len]=uu[i],arr[++len]=vv[i];
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
int temp=len;
rin(i,1,temp-1) arr[++len]=lca(arr[i],arr[i+1]);
arr[++len]=1;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
rin(i,1,len) id2[arr[i]]=i;
top=0;
rin(i,1,len){
ini[arr[i]]=true;
while(top&&id[sta[top]]+siz[sta[top]]-1<id[arr[i]]) add_iedge(id2[sta[top-1]],id2[sta[top]]),top--;
sta[++top]=arr[i];
}
while(top>1) add_iedge(id2[sta[top-1]],id2[sta[top]]),top--;
}
void dfs3(int x){
f[x][0]=f[x][1]=1;
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]) continue;
if(fa[ver]!=x) continue;
dfs3(ver);
f[x][0]=f[x][0]*(f[ver][0]+f[ver][1])%MOD;
f[x][1]=f[x][1]*f[ver][0]%MOD;
}
}
void getk(){
dfs3(1);
add_edge(0,1);
irin(i,len,1){
int x=arr[i],pre=fa[x],r=1;LL temp[2][2]={1,0,0,0};
while((!ini[x]||x==arr[i])&&x){
vis1[x]=true;r^=1;
if(x!=arr[i]) temp[r][0]=(temp[r^1][0]+temp[r^1][1])%MOD,temp[r][1]=temp[r^1][0];
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]) continue;
if(fa[ver]!=x) continue;
if(vis1[ver]) continue;
temp[r][0]=temp[r][0]*(f[ver][0]+f[ver][1])%MOD;
temp[r][1]=temp[r][1]*f[ver][0]%MOD;
}
pre=fa[pre],x=fa[x];
}
x=arr[i];
k[id2[x]][0][0]=(temp[r][0]+temp[r][1])%MOD;
k[id2[x]][1][0]=temp[r][0];
pre=fa[x],r=1;
memset(temp,0,sizeof temp);
temp[0][1]=1;
while((!ini[x]||x==arr[i])&&x){
vis2[x]=true;r^=1;
if(x!=arr[i]) temp[r][0]=(temp[r^1][0]+temp[r^1][1])%MOD,temp[r][1]=temp[r^1][0];
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]) continue;
if(fa[ver]!=x) continue;
if(vis2[ver]) continue;
temp[r][0]=temp[r][0]*(f[ver][0]+f[ver][1])%MOD;
temp[r][1]=temp[r][1]*f[ver][0]%MOD;
}
pre=fa[pre],x=fa[x];
}
x=arr[i];
k[id2[x]][0][1]=(temp[r][0]+temp[r][1])%MOD;
k[id2[x]][1][1]=temp[r][0];
}
}
void dfs4(int x){
f[x][0]=f[x][1]=1;
rin(i,0,(int)vec[x].size()-1){
int ver=vec[x][i];
dfs4(ver);
f[x][0]=f[x][0]*((f[ver][0]*k[ver][0][0]+f[ver][1]*k[ver][0][1])%MOD)%MOD;
f[x][1]=f[x][1]*((f[ver][0]*k[ver][1][0]+f[ver][1]*k[ver][1][1])%MOD)%MOD;
}
if(must[x]==0) f[x][1]=0;
else if(must[x]==1) f[x][0]=0;
}
void solve(){
rin(i,0,(1<<cnt)-1){
memset(must,-1,sizeof must);
bool flag=false;
rin(j,1,cnt){
int temp=((i>>(j-1))&1);
if(must[id2[uu[j]]]>-1&&must[id2[uu[j]]]!=temp){
flag=true;
break;
}
must[id2[uu[j]]]=temp;
if(must[id2[uu[j]]]==1){
if(must[id2[vv[j]]]==1){
flag=true;
break;
}
must[id2[vv[j]]]=0;
}
}
if(flag) continue;
dfs4(1);
ans=(ans+f[1][0]*k[1][0][0]+f[1][1]*k[1][0][1])%MOD;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("duliu.in","r",stdin);
freopen("duliu.out","w",stdout);
#endif
io>>n>>m;
rin(i,1,m){
int u,v;
io>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
dfs1(1,0,1);
dfs2(1,1);
build_itree();
getk();
solve();
printf("%lld\n",ans);
return 0;
}
[CF966E]May Holidays
分析
虚树和根号重构!
原题可以转化为:
将结点\(x\)到根的所有结点权值\(+1/-1\),并给结点\(x\)打上/去掉标记。
查询有多少个结点的权值为负且没有打标记。
考虑根号重构,每根号个操作重构虚树。虚树边,也就是原树上和这根号个修改无关的链可以一起处理。对于一条无关链上的所有结点的权值,直接排序后unique()
,记一下每种权值的个数,维护一个指针指向第一个权值\(\geq 0\)的位置,通过类似于打lazy标记的方式进行修改。修改的同时维护\(ans\)就好了。
也是超多细节,实现起来略微丧心病狂。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=100005;
int n,m,ecnt,head[MAXN];
int fa[MAXN],dep[MAXN],siz[MAXN],pc[MAXN],_top[MAXN],id[MAXN],tot;
int t[MAXN],opt[505],cnt,arr[1005],len,sta[1005],top;
int ifa[MAXN],ptr[MAXN],tag[MAXN],ans;
bool ini[MAXN],onv[MAXN];
std::vector<int> buc[MAXN],num[MAXN];
struct Edge{
int to,nxt;
}e[MAXN];
inline void add_edge(int bg,int ed){
ecnt++;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
}
inline void add_iedge(int bg,int ed){
ifa[ed]=bg;
}
void dfs1(int x,int pre,int depth){
fa[x]=pre;
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
dfs1(ver,x,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=ver;
pc[x]=ver;
}
}
}
void dfs2(int x,int topf){
_top[x]=topf;
id[x]=++tot;
if(!pc[x]) return;
dfs2(pc[x],topf);
trav(i,x){
int ver=e[i].to;
if(ver==pc[x]) continue;
dfs2(ver,ver);
}
}
inline int lca(int x,int y){
while(_top[x]!=_top[y]){
if(dep[_top[x]]<dep[_top[y]]) std::swap(x,y);
x=fa[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline bool cmp(int x,int y){
return id[x]<id[y];
}
void build_itree(){
rin(i,1,cnt) arr[i]=std::abs(opt[i]);
len=cnt;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
int temp=len;
rin(i,1,temp-1) arr[++len]=lca(arr[i],arr[i+1]);
arr[++len]=1;
std::sort(arr+1,arr+len+1,cmp);
len=std::unique(arr+1,arr+len+1)-arr-1;
top=0;
rin(i,1,len){
ini[arr[i]]=true;
while(top&&id[sta[top]]+siz[sta[top]]-1<id[arr[i]]) add_iedge(sta[top-1],sta[top]),top--;
sta[++top]=arr[i];
}
while(top>1) add_iedge(sta[top-1],sta[top]),top--;
}
void solve(){
rin(i,1,len){
int x=arr[i],cur=x;
while(x&&(!ini[x]||x==cur)){
if(x!=cur&&!onv[x]) buc[cur].push_back(t[x]);
x=fa[x];
}
sort(buc[cur].begin(),buc[cur].end());
rin(j,0,(int)buc[cur].size()-1){
if(j==0||buc[cur][j]!=buc[cur][j-1]){
num[cur].push_back(0),buc[cur][num[cur].size()-1]=buc[cur][j];
if(buc[cur][j]<0) ptr[cur]++;
}
++num[cur][num[cur].size()-1];
}
while(buc[cur].size()>num[cur].size()) buc[cur].pop_back();
}
rin(i,1,cnt){
int x=opt[i],cur=abs(x);
if(x>0){
onv[x]=true;
if(t[x]<0) ans--;
while(x){
t[x]--;tag[x]--;
if(ptr[x]!=buc[x].size()&&buc[x][ptr[x]]+tag[x]<0) ans+=num[x][ptr[x]],ptr[x]++;
if(t[x]==-1&&!onv[x]) ans++;
x=ifa[x];
}
}
else{
onv[x=-x]=false;
if(t[x]+1<0) ans++;
while(x){
t[x]++;tag[x]++;
if(ptr[x]&&buc[x][ptr[x]-1]+tag[x]>=0) --ptr[x],ans-=num[x][ptr[x]];
if(t[x]==0&&!onv[x]&&x!=cur) ans--;
x=ifa[x];
}
}
printf("%d ",ans);
}
rin(i,1,len){
int x=arr[i],cur=x;
while(x&&(!ini[x]||x==cur)){
if(x!=cur) t[x]+=tag[cur];
x=fa[x];
}
}
rin(i,1,len){
int x=arr[i];
buc[x].clear(),num[x].clear(),ifa[x]=ptr[x]=tag[x]=0,ini[x]=false;
}
len=0;
}
int main(){
n=read(),m=read();
rin(i,2,n) add_edge(read(),i);
rin(i,1,n) t[i]=read();
dfs1(1,0,1);
dfs2(1,1);
int lim=sqrt(m)+0.5;
rin(i,1,m){
opt[++cnt]=read();
if(cnt==lim){
build_itree();
solve();
cnt=0;
}
}
if(cnt){
build_itree();
solve();
}
printf("\n");
return 0;
}
[CF1073G]Surprise me!
暂时不想写了ToT。
虚树总结&题单&简要题解的更多相关文章
- 动态淀粉质(划掉)题单&简要题解
简介 动态点分治的思想:还不太清楚诶怎么办. 大概是通过降低树高来降低每次修改和询问的复杂度吧,还可以把树上一个连通块的信息统计到一个点(重心)上.具体实现方式和普通的静态点分治没有太大的区别,只是把 ...
- $FFT/NTT/FWT$题单&简要题解
打算写一个多项式总结. 虽然自己菜得太真实了. 好像四级标题太小了,下次写博客的时候再考虑一下. 模板 \(FFT\)模板 #include <iostream> #include < ...
- [Bzoj2286][Sdoi2011]消耗战(虚树模板题附讲解)
2286: [Sdoi2011]消耗战 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 4896 Solved: 1824[Submit][Statu ...
- 【BZOJ3879】SvT(后缀自动机,虚树)
[BZOJ3879]SvT(后缀自动机,虚树) 题面 BZOJ 题解 看着这个东西,询问若干个前缀两两之间的\(lcp\)? 显然\(lcp\)就是\(SAM\)构建出来的\(parent\)数上的\ ...
- Codeforces 863 简要题解
文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...
- 2018.09.25 bzoj3572: [Hnoi2014]世界树(虚树+树形dp)
传送门 虚树入门题? 好难啊. 在学习别人的写法之后终于过了. 这道题dp方程很好想. 主要是不好写. 简要说说思路吧. 显然最优值只能够从子树和父亲转移过来. 于是我们先dfs一遍用儿子更新父亲,然 ...
- 【CTSC2018】暴力写挂(边分治,虚树)
[CTSC2018]暴力写挂(边分治,虚树) 题面 UOJ BZOJ 洛谷 题解 发现第二棵树上的\(LCA\)的深度这玩意没法搞,那么枚举在第二棵树上的\(LCA\). 然后剩下的部分就是\(dep ...
- 洛谷P3233 世界树 [HNOI2014] 虚树
正解:虚树 解题报告: 传送门! 首先看到这种就要想到虚树这个是毫无疑问的QwQ 建虚树什么的都可以循规蹈矩地做,不说辣,具体可以看下虚树学习笔记什么的看下板子 但是建好虚树之后怎么搞还是有点儿讲究, ...
- 【BZOJ4912】天才黑客(最短路,虚树)
[BZOJ4912]天才黑客(最短路,虚树) 题面 BZOJ 洛谷 题解 \(Anson\)爷讲过的题目,然而我还是不会做 只有照着\(zsy\)的程序打我才会做....果然太弱了. 这道题目显然是把 ...
随机推荐
- Spring MVC 中使用AOP 进行统一日志管理--注解实现
1.AOP简介 AOP称为面向切面编程 AOP的基本概念 (1)Aspect(切面):通常是一个类,里面可以定义切入点和通知 (2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的 ...
- 多次NameNode执行format后DataNode启动不了解决方案
1.问题 执行start-dfs.sh后在进程中查看jps,发现NameNode启动,但DataNode没有 2.原因 在失败的.log文件中看到datanode的clusterID 和 nameno ...
- 使用render函数渲染组件
使用render函数渲染组件:https://www.jianshu.com/p/27ec4467a66b
- Failure to transfer org.apache.maven.plugins:maven-resources-plugin:pom:2.6 的解决办法
eclipse导入mavn工程报Failure to transfer org.apache.maven.plugins:maven-resources-plugin:pom:2.6 的解决办法: 错 ...
- POJ2387 Til the Cows Come Home (最短路 dijkstra)
AC代码 POJ2387 Til the Cows Come Home Bessie is out in the field and wants to get back to the barn to ...
- Mac 基于Anaconda的TensorFlow安装笔记
最近在中国大学MOOC平台学习北大的曹健老师上的“人工智能实践——Tensorflow”课程,开始我的人工智能之旅.第一天,讲解如何搭建实验室环境,我是mac系统,所以只写mac系统上的实验室环境安装 ...
- Vue源码解读-构造函数
src/core/instance/index.js此文件主要实现了Vue初始化 // 引入模块 import { initMixin } from './init' import { stateMi ...
- redis 学习导航
一.redis学习流程 二.redis官方网址: 官方网址:https://redis.io/ 三.redis简介 1. redis是一个基于内存,单线程的key-value的非关系型数据库,整个数据 ...
- ELK-全文检索技术-kibana操作elasticsearch
前言:建议kibana语法一定要学好! 1 软件安装 1.1 ES的安装 第一步:解压压缩包,放到一个没有中文没有空格的位置 第二步:修改配置文件 1. jvm.options ...
- php通过反射方法调用私有方法
PHP 5 具有完整的反射 API,添加了对类.接口.函数.方法和扩展进行反向工程的能力. 下面我们演示一下如何通过反射,来调用执行一个类中的私有方法: <?php //MyClass这个类中包 ...