算法
大体分这么几步:
- 递归地解决所有轻儿子,并删除记录的信息
- 解决重儿子,不删除记录
- 重新把所有轻儿子合并过来 复杂度为
Codeforces 208E Blood Cousins
思路
离线询问,对于每次询问,通过倍增找到的,在这个点上挂上这一次询问。在原树上进行一次树上启发式合并,记录深度为的点的个数,据此来回答当前点上挂的询问。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define en puts("")
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
void read() {}
void OP() {}
void op() {}
template <typename T, typename... T2>
inline void read(T &_, T2 &... oth)
{
int __=0;
_=0;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
__=1;
ch=getchar();
}
while(isdigit(ch))
{
_=_*10+ch-48;
ch=getchar();
}
_=__?-_:_;
read(oth...);
}
template <typename T>
void Out(T _)
{
if(_<0)
{
putchar('-');
_=-_;
}
if(_>=10)
Out(_/10);
putchar(_%10+'0');
}
template <typename T, typename... T2>
inline void OP(T _, T2... oth)
{
Out(_);
putchar('\n');
OP(oth...);
}
template <typename T, typename... T2>
inline void op(T _, T2... oth)
{
Out(_);
putchar(' ');
op(oth...);
}
/*#################################*/
const ll N=1E5+10;
ll n,m;
ll siz[N],f[N][20],dep[N],son[N],ans[N],t[N];
vector<ll> go[N];
vector<pii> qu[N];
void dfs(ll u)
{
siz[u]=1;
rep(i,0,16)
f[u][i+1]=f[f[u][i]][i];
for(auto v:go[u])
{
dep[v]=dep[u]+1;
f[v][0]=u;
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
ll find(ll u,ll dis)
{
for(int i=17;i>=0;--i)
{
if(dep[u]-dep[f[u][i]]<=dis)
{
dis-=dep[u]-dep[f[u][i]];
u=f[u][i];
}
}
return u;
}
void update(ll u,ll opt)
{
t[dep[u]]+=opt;
for(auto v:go[u])
update(v,opt);
}
void dsu(ll u,ll opt)
{
for(auto v:go[u])
{
if(v==son[u])
continue;
dsu(v,0);
}
if(son[u])
dsu(son[u],1);
for(auto v:go[u])
{
if(v==son[u])
continue;
update(v,1);
}
++t[dep[u]];
for(auto it:qu[u])
{
ll absdep=it.first+dep[u];
ans[it.second]=t[absdep]-1;
}
if(!opt)
update(u,-1);
}
void print()
{
rep(i,1,n)
op(find(i,1));
en;
}
int main()
{
read(n);
ll fa,u,v;
rep(i,1,n)
{
read(fa);
if(!fa)
continue;
go[fa].emplace_back(i);
}
rep(i,1,n)
{
if(!dep[i])
{
dep[i]=1;
dfs(i);
}
}
read(m);
rep(i,1,m)
{
read(u,v);
if(dep[u]<=v)
{
ans[i]=0;
continue;
}
ll anc=find(u,v);
qu[anc].emplace_back(pii(v,i));
}
rep(i,1,n)
{
if(dep[i]!=1)
continue;
dsu(i,0);
}
rep(i,1,m)
op(ans[i]);
en;
}
Codeforces 246E Blood Cousins Return
思路
离线询问,启发式合并时为每个深度维护一个,把所有名字插入对应深度的,的大小就是该深度的答案。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define en puts("")
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
void read() {}
void OP() {}
void op() {}
template <typename T, typename... T2>
inline void read(T &_, T2 &... oth)
{
int __=0;
_=0;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
__=1;
ch=getchar();
}
while(isdigit(ch))
{
_=_*10+ch-48;
ch=getchar();
}
_=__?-_:_;
read(oth...);
}
template <typename T>
void Out(T _)
{
if(_<0)
{
putchar('-');
_=-_;
}
if(_>=10)
Out(_/10);
putchar(_%10+'0');
}
template <typename T, typename... T2>
inline void OP(T _, T2... oth)
{
Out(_);
putchar('\n');
OP(oth...);
}
template <typename T, typename... T2>
inline void op(T _, T2... oth)
{
Out(_);
putchar(' ');
op(oth...);
}
/*#################################*/
const ll N=1E5+10,base=233,P=998244353;
ll n,m;
ll siz[N],son[N],dep[N],a[N],ans[N];
vector<ll> go[N];
set<pii> se[N];
vector<pii> qu[N];
string s;
void dfs(ll u)
{
siz[u]=1;
for(auto v:go[u])
{
dep[v]=dep[u]+1;
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
void insert(ll u)
{
auto it=se[dep[u]].lower_bound(pii(a[u],0));
if(it!=se[dep[u]].end() && (*it).first==a[u])
{
ll tmp=(*it).second;
se[dep[u]].erase(it);
se[dep[u]].insert(pii(a[u],tmp+1));
}
else
se[dep[u]].insert(pii(a[u],1));
}
void update(ll u,ll opt)
{
if(opt==1)
insert(u);
else
se[dep[u]].clear();
for(auto v:go[u])
update(v,opt);
}
void dsu(ll u,ll opt)
{
for(auto v:go[u])
{
if(v==son[u])
continue;
dsu(v,0);
}
if(son[u])
dsu(son[u],1);
for(auto v:go[u])
{
if(v==son[u])
continue;
update(v,1);
}
insert(u);
for(auto it:qu[u])
{
ll absdep=dep[u]+it.first;
if(absdep<=1e5)
ans[it.second]=se[absdep].size();
}
if(!opt)
update(u,-1);
}
int main()
{
read(n);
ll u,v,fa;
rep(i,1,n)
{
cin>>s;
ll hash=0;
for(auto ch:s)
hash=(hash*base+ch)%P;
a[i]=hash;
read(fa);
if(fa==0)
continue;
go[fa].emplace_back(i);
}
rep(i,1,n)
{
if(!siz[i])
{
dep[i]=1;
dfs(i);
}
}
read(m);
rep(i,1,m)
{
read(u,v);
qu[u].emplace_back(pii(v,i));
}
rep(i,1,n)
if(dep[i]==1)
dsu(i,0);
rep(i,1,m)
OP(ans[i]);
en;
}
Codeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
思路
能够重新打乱形成回文串的字符集必然满足要么所有字母出现次数均为偶数,要么仅有一种字母出现了奇数次。
因为只有个字母,且仅考虑它们出现次数的奇偶性,可以把每个字母的奇偶性压缩成型二进制的一位,如果第个字母出现了奇数次,那么第位为,反之为。
考虑对于树上每个节点维护一个二进制下22位的前缀状态,表示从根节点到当前点路径上每一种字母出现次数的奇偶性状态。那么对于树上任何两个点,它们之间路径上每一种字母的出现次数显然是,只要该值为,那么以这两个点为端点的路径一定可以重新组合成为回文串。
另一种情况是异或后仅剩一位是,这种情况也是可行的。
现在进行树上启发式合并,对于已经合并过的节点,维护前缀状态为时的最大深度(因为我们要找最长链,显然深度越大链越长)。每次合并一个轻儿子上的节点,先找之前节点前缀状态为的最大深度来维护答案,然后枚举每一位多异或一个(也就是上面说的另一种情况)得到的最大深度。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define en puts("")
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
void read() {}
void OP() {}
void op() {}
template <typename T, typename... T2>
inline void read(T &_, T2 &... oth)
{
int __=0;
_=0;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
__=1;
ch=getchar();
}
while(isdigit(ch))
{
_=_*10+ch-48;
ch=getchar();
}
_=__?-_:_;
read(oth...);
}
template <typename T>
void Out(T _)
{
if(_<0)
{
putchar('-');
_=-_;
}
if(_>=10)
Out(_/10);
putchar(_%10+'0');
}
template <typename T, typename... T2>
inline void OP(T _, T2... oth)
{
Out(_);
putchar('\n');
OP(oth...);
}
template <typename T, typename... T2>
inline void op(T _, T2... oth)
{
Out(_);
putchar(' ');
op(oth...);
}
/*#################################*/
const ll N=5E5+10,M=(1<<22)+5;
ll n,tot;
ll head[N],sum[N],siz[N],son[N],dep[N],ans[N],pre[30],ma[M];
struct Edge{
ll nxt,to,w;
}e[N];
void add_edge(ll u,ll v,ll w,int flag)
{
e[++tot]=(Edge){head[u],v,w};
head[u]=tot;
if(flag)
add_edge(v,u,w,0);
}
void dfs(ll u,ll sta)
{
sum[u]=sta;
siz[u]=1;
bl(u,i)
{
ll v=e[i].to;
dep[v]=dep[u]+1;
dfs(v,sta^pre[e[i].w]);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
void merge(ll u,ll top)
{
if(ma[sum[u]])
ans[top]=max(ans[top],ma[sum[u]]+dep[u]-2*dep[top]);
rep(i,1,22)
if(ma[sum[u]^pre[i]])
ans[top]=max(ans[top],ma[sum[u]^pre[i]]+dep[u]-2*dep[top]);
bl(u,i)
{
ll v=e[i].to;
merge(v,top);
}
}
void update(ll u,ll opt)
{
if(opt==1)
ma[sum[u]]=max(ma[sum[u]],dep[u]);
else
ma[sum[u]]=0;
bl(u,i)
{
ll v=e[i].to;
update(v,opt);
}
}
void dsu(ll u,ll opt)
{
bl(u,i)
{
ll v=e[i].to;
if(v==son[u])
continue;
dsu(v,0);
ans[u]=max(ans[u],ans[v]);
}
if(son[u])
{
dsu(son[u],1);
ans[u]=max(ans[u],ans[son[u]]);
}
ans[u]=max(ans[u],ma[sum[u]]-dep[u]);
rep(i,1,22)
ans[u]=max(ans[u],ma[sum[u]^pre[i]]-dep[u]);
ma[sum[u]]=max(ma[sum[u]],dep[u]);
bl(u,i)
{
ll v=e[i].to;
if(v==son[u])
continue;
merge(v,u);
update(v,1);
}
if(!opt)
update(u,-1);
}
void print()
{
rep(i,1,n)
op(sum[i]);
en;
rep(i,1,n)
op(dep[i]);
en;
}
int main()
{
read(n);
rep(i,1,22)
pre[i]=1ll<<(i-1);
ll fa;
char ch;
rep(i,2,n)
{
read(fa);
scanf("%c",&ch);
add_edge(fa,i,ch-'a'+1,0);
}
dfs(1,0);
dsu(1,1);
rep(i,1,n)
op(ans[i]);
en;
}
2019 ICPC 南昌站 K Tree
思路
树上启发式合并,从小到大维护权值为的节点的深度,每次把一个权值为新节点合并到权值为的节点上,就在权值为的节点中二分一个深度最大且与当前点距离不超过的位置,小于这个点的所有点都可以和当前点贡献答案。因为这一段操作涉及到增删,查,所以开个平衡树来维护。
代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace __gnu_pbds;
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define en puts("")
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<int,int>
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
const ll N=2E5+10;
int n,k;
ll ans;
int w[N],son[N],siz[N],dep[N];
vector<int> go[N];
tree<pii,null_type,less<pii>,rb_tree_tag,tree_order_statistics_node_update> t[N];
void dfs(int u)
{
siz[u]=1;
for(auto v:go[u])
{
dep[v]=dep[u]+1;
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
void merge(int u,int lca)
{
int x=2*w[lca]-w[u];
if(x>=0)
ans+=t[x].order_of_key(pii(2*dep[lca]-dep[u]+k,2e9));
for(auto &v:go[u])
merge(v,lca);
}
void update(int u,int opt)
{
if(opt==1)
t[w[u]].insert(pii(dep[u],u));
else if(opt==-1)
t[w[u]].erase(pii(dep[u],u));
for(auto v:go[u])
update(v,opt);
}
void dsu(int u,bool opt)
{
for(auto v:go[u])
{
if(v==son[u])
continue;
dsu(v,0);
}
if(son[u])
dsu(son[u],1);
for(auto v:go[u])
{
if(v==son[u])
continue;
merge(v,u);
update(v,1);
}
t[w[u]].insert(pii(dep[u],u));
if(!opt)
update(u,-1);
}
int main()
{
scanf("%d%d",&n,&k);
rep(i,1,n)
scanf("%d",w+i);
int u;
rep(v,2,n)
{
scanf("%d",&u);
go[u].push_back(v);
}
// dep[1]=1;
dfs(1);
dsu(1,1);
printf("%lld\n",ans*2ll);
}