bzoj 4911: [Sdoi2017]切树游戏
考虑维护原树的lct,在上面dp,由于dp方程特殊,均为异或卷积或加法,计算中可以只使用fwt后的序列
v[w]表示联通子树的最浅点为w,且不选w的splay子树中的点
l[w]表示联通子树的最浅点在w的lct子树中,且选w的splay子树中极左点(w的splay子树为{w}+{u的splay子树,满足u==ch[w][0]||u==ch[w][1]})
r[w]表示联通子树的最浅点在w的lct子树中,且选w的splay子树中极右点
lr[w]表示联通子树的最浅点为w,且选w的splay子树中所有的点
s[w]表示联通子树的最浅点在w的lct子树中(w的lct子树为{w}+{u的lct子树,满足fa[u]==w})
ss[w]表示联通子树的最浅点在w的lct子树中,且不选w
时间复杂度O((n+q)mlogm),由于dp方程复杂以及lct自带的常数,总体常数较大
#include<bits/stdc++.h>
#define fm(i) for(int i=0;i<m;++i)
typedef int arr[];
const int N=,P=;
int n,m,v0[N],ivs[],*iv=ivs+P+;
arr x0[],ans,tmp;
std::vector<int>e[N];
struct node{
node*c[],*f;
arr v,vt,l,r,lr,s,ss;
bool nrt();
void up();
void setrc(node*w);
}ns[N],*nil=ns,*rt=ns+;
void cpy(int*a,int*b){memcpy(a,b,sizeof(int)*m);}
bool node::nrt(){return this==f->c[]||this==f->c[];}
void node::up(){
fm(i){
int vi=vt[i]?:v[i];
int vc0lr=vi*c[]->lr[i]%P;
int vc0r=vi*c[]->r[i]%P;
l[i]=(vc0lr*c[]->l[i]+c[]->l[i])%P;
r[i]=(vc0r*c[]->lr[i]+c[]->r[i])%P;
lr[i]=vc0lr*c[]->lr[i]%P;
s[i]=(vc0r*c[]->l[i]+ss[i])%P;
}
}
void mul(int*v1,int*t,int*v2){fm(i)v2[i]?v1[i]=v1[i]*v2[i]%P:++t[i];}
void div(int*v1,int*t,int*v2){fm(i)v2[i]?v1[i]=v1[i]*iv[v2[i]]%P:--t[i];}
void node::setrc(node*w){
if(c[]!=nil)mul(v,vt,c[]->l);
c[]=w;
if(c[]!=nil)div(v,vt,c[]->l);
up();
}
void rot(node*w){
node*f=w->f,*g=f->f;
int d=w==f->c[];
if(f->nrt())g->c[g->c[]==f]=w;
w->f=g;
(f->c[d]=w->c[d^])->f=f;
(w->c[d^]=f)->f=w;
fm(i){
int x=f->ss[i],y=f->c[d]->s[i];
f->ss[i]=(x-w->s[i]+y)%P;
w->s[i]=f->s[i];
w->ss[i]-=y;
}
f->up();
fm(i)w->ss[i]=(w->ss[i]+f->s[i])%P;
}
void sp(node*w){
if(!w->nrt())return;
do{
node*f=w->f;
if(f->nrt())rot((f->c[]==w)==(f->f->c[]==f)?f:w);
rot(w);
}while(w->nrt());
w->up();
}
void acs(node*x){
rt=x;
for(node*y=nil;x!=nil;sp(x),x->setrc(y),y=x,x=x->f);
sp(rt);
}
void dfs(int w,int pa){
for(int i=;i<e[w].size();++i){
int u=e[w][i];
if(u!=pa){
dfs(u,w);
ns[u].f=ns+w;
mul(ns[w].v,ns[w].vt,ns[u].l);
fm(j)ns[w].ss[j]+=ns[u].s[j];
}
}
ns[w].up();
}
void dwt(int*a){
for(int i=;i<m;i<<=){
for(int j=;j<m;j+=i<<){
int*b=a+j,*c=b+i;
for(int k=;k<i;++k){
int x=b[k],y=c[k];
b[k]=x+y;
c[k]=x-y;
}
}
}
fm(i)a[i]%=P;
}
char buf[],*ptr=buf;
int _(){
int x=;
while(*ptr<)++ptr;
while(*ptr>)x=x*+*ptr++-;
return x;
}
int main(){
fread(buf,,sizeof(buf),stdin);
n=_();m=_();
for(int i=;i<m;++i){
x0[i][i]=;
dwt(x0[i]);
}
iv[-P]=iv[]=;
for(int i=;i<P;++i)iv[i-P]=iv[i]=(P-P/i)*iv[P%i]%P;
for(int i=;i<=n;++i)ns[i]=(node){nil,nil,nil};
cpy(nil->l,x0[]);
cpy(nil->r,x0[]);
cpy(nil->lr,x0[]);
for(int i=;i<=n;++i){
v0[i]=_();
cpy(ns[i].v,x0[v0[i]]);
}
for(int i=,a,b;i<n;++i){
a=_(),b=_();
e[a].push_back(b);
e[b].push_back(a);
}
dfs(,);
for(int q=_(),ed=;q;--q){
if(_()==){
if(ed){
cpy(ans,rt->s);
dwt(ans);
ed=;
}
printf("%d\n",(ans[_()]*iv[m]%P+P)%P);
}else{
ed=;
int w=_(),x=_();
acs(ns+w);
int*v1=x0[v0[w]],*v2=x0[x];
fm(i)rt->v[i]=rt->v[i]*v1[i][iv]%P*v2[i]%P;
fm(i)ans[i]=rt->vt[i]?:rt->v[i];
dwt(ans);
v0[w]=x;
rt->up();
}
}
return ;
}
由于树形态不改变,也可以建一棵深度不超过logn+O(1)的静态lct,可以明显减小常数
#include<bits/stdc++.h>
#define fm(i) for(int i=0;i<m;++i)
typedef int arr[];
const int N=,P=;
int n,m,v0[N],ivs[],*iv=ivs+P+;
arr x0[],ans,tmp;
std::vector<int>e[N];
struct node{
node*c[],*f;
arr v,vt,l,r,lr,s,ss;
bool isrt();
void up();
}ns[N],*nil=ns,*rt;
bool node::isrt(){return this!=f->c[]&&this!=f->c[];}
void cpy(int*a,int*b){memcpy(a,b,sizeof(int)*m);}
void node::up(){
fm(i){
int vi=vt[i]?:v[i];
int vc0lr=vi*c[]->lr[i]%P;
int vc0r=vi*c[]->r[i]%P;
l[i]=(vc0lr*c[]->l[i]+c[]->l[i])%P;
r[i]=(vc0r*c[]->lr[i]+c[]->r[i])%P;
lr[i]=vc0lr*c[]->lr[i]%P;
s[i]=(vc0r*c[]->l[i]+ss[i])%P;
}
}
void mul(int*v1,int*t,int*v2){fm(i)v2[i]?v1[i]=v1[i]*v2[i]%P:++t[i];}
void div(int*v1,int*t,int*v2){fm(i)v2[i]?v1[i]=v1[i]*iv[v2[i]]%P:--t[i];}
int fa[N],sz[N],son[N],dep[N],top[N],ws[N],wp;
void f1(int w,int pa){
dep[w]=dep[fa[w]=pa]+(sz[w]=);
for(int i=;i<e[w].size();++i){
int u=e[w][i];
if(u!=pa){
f1(u,w);
sz[w]+=sz[u];
if(sz[u]>sz[son[w]])son[w]=u;
}
}
ns[w].up();
}
node*build(int L,int R,node*f){
if(L>R)return nil;
int L0=L,R0=R;
for(int M,s0=ws[L][sz]+ws[R+][sz];L<R;ws[M=L+R+>>][sz]*<s0?R=M-:L=M);
node*w=ns+ws[L];
w->c[]=build(L0,L-,w);
w->c[]=build(L+,R0,w);
w->f=f;
w->up();
if(f!=nil){
fm(i)f->ss[i]+=w->s[i];
if(L0==&&R0==wp)mul(f->v,f->vt,w->l);
}
return w;
}
void f2(int w,int tp){
top[w]=tp;
for(int i=;i<e[w].size();++i){
int u=e[w][i];
if(u!=fa[w]&&u!=son[w])f2(u,u);
}
if(son[w])f2(son[w],tp);
else{
wp=;
for(int a=tp;a;a=son[a])ws[++wp]=a;
ws[wp+]=;
rt=build(,wp,ns+fa[tp]);
}
}
void dwt(int*a){
for(int i=;i<m;i<<=){
for(int j=;j<m;j+=i<<){
int*b=a+j,*c=b+i;
for(int k=;k<i;++k){
int x=b[k],y=c[k];
b[k]=x+y;
c[k]=x-y;
}
}
}
fm(i)a[i]%=P;
}
char buf[],*ptr=buf;
int _(){
int x=;
while(*ptr<)++ptr;
while(*ptr>)x=x*+*ptr++-;
return x;
}
int main(){
fread(buf,,sizeof(buf),stdin);
n=_();m=_();
for(int i=;i<m;++i){
x0[i][i]=;
dwt(x0[i]);
}
iv[-P]=iv[]=;
for(int i=;i<P;++i)iv[i-P]=iv[i]=(P-P/i)*iv[P%i]%P;
for(int i=;i<=n;++i)ns[i]=(node){nil,nil,nil};
cpy(nil->l,x0[]);
cpy(nil->r,x0[]);
cpy(nil->lr,x0[]);
for(int i=;i<=n;++i){
v0[i]=_();
cpy(ns[i].v,x0[v0[i]]);
}
for(int i=,a,b;i<n;++i){
a=_(),b=_();
e[a].push_back(b);
e[b].push_back(a);
}
f1(,);f2(,);
for(int q=_(),ed=;q;--q){
if(_()==){
if(ed){
cpy(ans,rt->s);
dwt(ans);
ed=;
}
printf("%d\n",(ans[_()]*iv[m]%P+P)%P);
}else{
ed=;
int w=_(),x=_(),stp=;
node*_w=ns+w,*stk[];
for(node*a=_w;a!=nil;stk[++stp]=a,a=a->f);
for(int i=stp;i>;--i){
int*v1=stk[i]->ss,*v2=stk[i-]->s;
fm(j)v1[j]-=v2[j];
if(stk[i-]->isrt())div(stk[i]->v,stk[i]->vt,stk[i-]->l);
}
int*v1=x0[v0[w]],*v2=x0[x];
fm(i)_w->v[i]=_w->v[i]*v1[i][iv]%P*v2[i]%P;
_w->up();
for(int i=;i<=stp;++i){
int*v1=stk[i]->ss,*v2=stk[i-]->s;
fm(j)v1[j]=(v1[j]+v2[j])%P;
if(stk[i-]->isrt())mul(stk[i]->v,stk[i]->vt,stk[i-]->l);
stk[i]->up();
}
v0[w]=x;
}
}
return ;
}
bzoj 4911: [Sdoi2017]切树游戏的更多相关文章
- 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)
[BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...
- BZOJ4911: [Sdoi2017]切树游戏
BZOJ 4911 切树游戏 重构了三次.jpg 每次都把这个问题想简单了.jpg 果然我还是太菜了.jpg 这种题的题解可以一眼秒掉了,FWT+动态DP简直是裸的一批... 那么接下来,考虑如何维护 ...
- LG3781 [SDOI2017]切树游戏
题意 题目描述 小Q是一个热爱学习的人,他经常去维基百科学习计算机科学. 就在刚才,小Q认真地学习了一系列位运算符,其中按位异或的运算符\(\oplus\)对他影响很大.按位异或的运算符是双目运算符. ...
- LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】
题目分析: 好题.本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法. 首先考虑静态的情况.这个的DP方程非常容易写出来. 接着可以注意到对于异或结果的计数可以看成一个FW ...
- [SDOI2017]切树游戏
题目 二轮毒瘤题啊 辣鸡洛谷竟然有卡树剖的数据 还是\(loj\)可爱 首先这道题没有带修,设\(dp_{i,j}\)表示以\(i\)为最高点的连通块有多少个异或和为\(j\),\(g_{i,j}=\ ...
- 洛谷 P3781 - [SDOI2017]切树游戏(动态 DP+FWT)
洛谷题面传送门 SDOI 2017 R2 D1 T3,nb tea %%% 讲个笑话,最近我在学动态 dp,wjz 在学 FWT,而我们刚好在同一天做到了这道题,而这道题刚好又是 FWT+动态 dp ...
- 【LOJ】#2269. 「SDOI2017」切树游戏
题解 把所有的数组一开始就FWT好然后再IFWT回去可以减小常数 从13s跑到0.7s-- 可以参照immortalCO的论文,感受一下毒瘤的动态动态DP 就是用数据结构维护线性递推的矩阵的乘积 由于 ...
- loj#2269. 「SDOI2017」切树游戏
还是loj的机子快啊... 普通的DP不难想到,设F[i][zt]为带上根玩出zt的方案数,G[i][zt]为子树中的方案数,后面是可以用FWT优化的 主要是复习了下动态DP #include< ...
- LOJ2269. 「SDOI2017」切树游戏 [FWT,动态DP]
LOJ 思路 显然是要DP的.设\(dp_{u,i}\)表示\(u\)子树内一个包含\(u\)的连通块异或出\(i\)的方案数,发现转移可以用FWT优化,写成生成函数就是这样的: \[ dp_{u}= ...
随机推荐
- <NET CLR via c# 第4版>笔记 第15章 枚举类型和位标志
15.1 枚举类型 枚举定义的符号是常量值. C#编译器编译时,会用数值替换符号,不再引用定义了符号的枚举类型.可能会出现一些版本问题. Enum.IsDefined(Type enumType, o ...
- PHP实现日志处理类库 - 【微信开发之微电商网站】技术笔记之二
继上篇文章[微信开发之微电商网站]技术笔记之一,昨日做了日志处理的功能. 对于现在的应用程序来说,日志的重要性是不言而喻的.很难想象没有任何日志记录功能的应用程序运行在生产环境中.日志所能提供的功能是 ...
- STL标准库-Move对容器效率的影响
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 C++11新增move()语法(我暂时交错右值引用),在前面我有一篇文章叫 C++11_右值引用 简单的介绍了右值引用类的实现, ...
- Engineering Management
Engineering Management 工程師面對工作或挑戰時,可應用以下法則一步一步把工作規管和實施. 1. Planning 計劃 2. Organization 組織 ...
- 插件PageHelper实现分页查询
一,需求: CommonQuery--PyQueryBean PyQueryBean:鹏飞历史记录查询,以往哪些人对征信进行了查询.CommonQuery:查询条件:根据查询人(umName).被查询 ...
- $(function(){})与(function($){})(jquery)
$(function(){}) function中的代码在DOM加载完成后立即执行,比window.onload()更快,因为前者不需要等待图片加载完成. (function($){})(jquery ...
- select into from和insert into from
最近在研究oracle function 时发现select into from和insert into from,这样的语句,于是上网查阅资料学习了一下, 原来两种表达式均可以达到复制整个表或表的一 ...
- 使用Ant搭建Android开发环境入门
使用Ant搭建Android开发环境入门 使用Ant搭建Android开发环境,建立android项目 配置Ant环境 下载Ant:http://ant.apache.org/bindownloa ...
- 算法训练 P1103
算法训练 P1103 时间限制:1.0s 内存限制:256.0MB 编程实现两个复数的运算.设有两个复数 和 ,则他们的运算公式为: 要求:(1)定义一个结构体类型来描述复数. ...
- 第三篇 makefile的伪目标
我们来思考一下makefile中的目标究竟是什么?实际上,在默认情况下: 1.make将makefile的目标认为是一个文件: 2.make解释器比较目标文件和依赖文件的新旧关系,决定是否 ...