洛谷题面传送门

SDOI 2017 R2 D1 T3,nb tea %%%

讲个笑话,最近我在学动态 dp,wjz 在学 FWT,而我们刚好在同一天做到了这道题,而这道题刚好又是 FWT+动态 dp

首先考虑怎样暴力计算答案,我们记 \(dp_{u,j}\) 表示以 \(u\) 为根的子树中有多少个连通块包含 \(u\) 且权值的异或和为 \(j\),初始 \(dp_{u,val_u}=1\),每次遍历 \(u\) 的一个子树 \(v\) 就对这个子树就对这两个子树的 \(dp\) 做一个合并,即 \(dp_{u,x}\leftarrow dp_{u,x}+\sum\limits_{y=0}^{m-1}dp_{u,y}\times dp_{v,x\oplus y}\),最终答案即为 \(\sum\limits_{u}dp_{u,k}\)。正确性显然,时间复杂度 \(\mathcal O(qnm^2)\),可以通过前四个测试点。

考虑优化,首先一个非常明显的优化是,DP 转移方程式长得一脸 xor 卷积的样子,如果我们记 \(f*g\) 表示 \(f,g\) 两个集合幂级数的 FWTxor,那么上述式子可以改写为 \(dp_u=dp_u+dp_u*dp_v=dp_u*(dp_v+1)\),因此考虑将所有 \(dp_u\) 都变为 \(\text{FWT}(dp_u)\),那么 \(dp_u\) 的初始值就变为 \(dp_{u,i}=\text{FWT}(E_{val_u})_i\),其中 \(E_i\) 为满足 \(f_i=1,f_j=0(j\ne i)\) 的集合幂级数 \(f\),这个可以通过预处理所有 \(\text{FWT}(E_i)\) 实现 \(\mathcal O(m)\) 初始化。转移操作可以根据 FWT 那一套理论变成 \(dp_{u,i}\leftarrow dp_{u,i}\times(dp_{v,i}+1)\),这样即可实现 \(\mathcal O(m)\) 转移。然后每次操作完了之后再 IFWT 回来即可,时间复杂度降到了 \(\mathcal O(qnm+qm\log m)\),还是只能通过前四个测试点(

注意到上述 \(dp\) 对于不带修改的情况是 efficient enough 的,但是带上修改就直接萎掉了,因此考虑擅长处理修改操作的动态 \(dp\) 来解决这个问题,按照动态 \(dp\) 的套路我们将树剖成一条条重链,\(dp\) 分为轻儿子和重儿子处理,我们记 \(dpl_{u,i}=\sum\limits_{v\in\text{lightson}(u)}(dp_{v,i}+1)\),那么记 \(w=wson_u\),则有 \(dp_{u,i}=dpl_{u,i}\times\text{FWT}(E_{val_u})_i\times (dp_{w,i}+1)\)。

但是光记录一个 \(dp\) 值是远远不够的,因为最终我们要求的是整棵子树中 \(dp_{u,k}\) 的值之和,所有我们不得不再额外记录 \(sum_{u,i}\) 表示子树中所有点的 \(dp_{u,i}\) 之和,那么有 \(sum_{u,i}=\sum\limits_{v\in \text{son}(u)}sum_{v,i}+dp_{u,i}\),按照套路我们还是记 \(suml_{u,i}=\sum\limits_{v\in\text{lightson}(u)}sum_{v,i}\),那么有 \(sum_{u,i}=sum_{w,i}+dp_{u,i}+suml_{u,i}=sum_{w,i}+dpl_{u,i}\times\text{FWT}(E_{val_u})_i\times(dp_{w,i}+1)+suml_{u,i}\)

考虑将这东西写成矩阵的形式,那么有:

\[\begin{bmatrix}dp_{u}&sum_{u}&1\end{bmatrix}=\begin{bmatrix}dp_{w}&sum_{w}&1\end{bmatrix}\times
\begin{bmatrix}
dpl_u\times\text{FWT}(E_{val_u})&dpl_u\times\text{FWT}(E_{val_u})&0\\
0&1&0\\
dpl_u\times\text{FWT}(E_{val_u})&dpl_u\times\text{FWT}(E_{val_u})+suml_u&1
\end{bmatrix}
\]

其中 \(f\times g\) 就对应项相乘好了,\(f+g\) 也同理。

记 \(A_u=\begin{bmatrix}
dpl_u\times\text{FWT}(E_{val_u})&dpl_u\times\text{FWT}(E_{val_u})&0\\
0&1&0\\
dpl_u\times\text{FWT}(E_{val_u})&dpl_u\times\text{FWT}(E_{val_u})+suml_u&1
\end{bmatrix}\),那么对于一个点 \(u\) 而言,记它到重链底经过的节点依次是 \(u=v_1,v_2,\cdots,v_k\),那么有

\[\begin{bmatrix}dp_{u}&sum_{u}&1\end{bmatrix}=\begin{bmatrix}0&0&1\end{bmatrix}\times\prod\limits_{i=k}^1A_{v_i}
\]

这个可以树链剖分+线段树维护。

修改操作就按照动态 \(dp\) 的套路不断跳重链并撤销原来的 \(dp_{top_u}\) 对 \(dpl_{fa[top_u]}\) 和 \(suml_{fa[top_u]}\) 的影响并加入新的贡献即可,时间复杂度 \(q\log^2nm+qm\log m\),LOJ 上可以通过,而洛谷上由于某两位毒瘤提供的毒瘤卡树剖的数据,只能获得 \(80\) 分的好成绩。

说起来轻巧,实现起来一堆细节需要注意:

  1. 直接矩阵乘法会多 \(27\) 的常数,导致 TLE,因此需要按照套路进行优化,注意到这个 \(3\times 3\) 的矩阵中只有四个位置是有用的,因此可以只维护这四个位置的值,即 \(\begin{bmatrix}a&b&0\\0&1&0\\c&d&1\end{bmatrix}\),那么有 \(\begin{bmatrix}a_1&b_1&0\\0&1&0\\c_1&d_1&1\end{bmatrix}\times \begin{bmatrix}a_2&b_2&0\\0&1&0\\c_2&d_2&1\end{bmatrix}=\begin{bmatrix}a_1a_2&a_1b_2+b_1&0\\0&1&0\\a_2c_1+c_2&b_2c_1+d_1+d_2&1\end{bmatrix}\),这样常数可以降到 \(4\)。
  2. 注意线段树 pushup 的顺序。
  3. 在撤销原来的贡献时会出现除以 \(0\) 的情况,因此可以将 \(dpl_{u,i}\) 存成一个个结构体,每个结构体用 \(x\times 0^y\) 表示一个数,每次乘以 \(0\) 时令 \(y\) 加一,除以 \(0\) 则令 \(y\) 减一,这样可以避免这个问题(u1s1 蒟蒻是第一次遇到这个套路呢,大佬不喜勿喷)
  4. 注意计算新加入的贡献时是计算线段树上 \([dfn[top[x]]],dfn[bot[top[x]]]\) 内矩阵的乘积,而不是 \([dfn[x]],dfn[bot[top[x]]]\),蒟蒻因为这个错误调了 1h,心态炸裂。

码了 212 行……

const int MAXN=3e4;
const int MAXV=1<<7;
const int MOD=1e4+7;
const int INV2=5004;
int n,m,val[MAXN+5],inv[MOD+4];
void getinv(){
for(int i=(inv[0]=inv[1]=1)+1;i<MOD;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
}
void FWTxor(int *a,int len,int type){
for(int i=2;i<=len;i<<=1)
for(int j=0;j<len;j+=i)
for(int k=0;k<(i>>1);k++){
int X=a[j+k],Y=a[(i>>1)+j+k];
if(~type) a[j+k]=(X+Y)%MOD,a[(i>>1)+j+k]=(X-Y+MOD)%MOD;
else a[j+k]=(X+Y)*INV2%MOD,a[(i>>1)+j+k]=(X-Y+MOD)*INV2%MOD;
}
}
struct num0{//number expressed as x*0^y
int x,y;
num0(int v=1){(!v)?(y=x=1):(x=v,y=0);}
num0 operator *(const int &rhs){
(!rhs)?(++y):(x=x*rhs%MOD);
return *this;
}
num0 operator /(const int &rhs){
(!rhs)?(--y):(x=x*inv[rhs]%MOD);
return *this;
}
int num(){return y?0:x;}
};
struct poly{
int a[MAXV+5];
poly(){memset(a,0,sizeof(a));}
poly(int x){for(int i=0;i<m;i++) a[i]=x;}
poly operator +(poly rhs) const{
poly res;
for(int i=0;i<m;i++) res.a[i]=(a[i]+rhs.a[i])%MOD;
return res;
}
poly operator *(poly rhs) const{
poly res(1);
for(int i=0;i<m;i++) res.a[i]=a[i]*rhs.a[i]%MOD;
return res;
}
void FWT(){FWTxor(a,m,1);}
void IFWT(){FWTxor(a,m,-1);}
} e[MAXV+5];
int 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;}
int siz[MAXN+5],fa[MAXN+5],dep[MAXN+5],wson[MAXN+5];
int top[MAXN+5],dfn[MAXN+5],tim=0,rid[MAXN+5];
int bot[MAXN+5];
void dfs1(int x=1,int f=0){
siz[x]=1;fa[x]=f;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs1(y,x);siz[x]+=siz[y];
if(siz[y]>siz[wson[x]]) wson[x]=y;
}
}
void dfs2(int x=1,int tp=1){
top[x]=tp;rid[dfn[x]=++tim]=x;if(wson[x]) dfs2(wson[x],tp);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==wson[x]||y==fa[x]) continue;
dfs2(y,y);
}
}
int f[MAXN+5][MAXV+5],sum[MAXN+5][MAXV+5],suml[MAXN+5][MAXV+5];
num0 fl[MAXN+5][MAXV+5];
void dfs3(int x=1){
for(int i=0;i<m;i++) f[x][i]=e[val[x]].a[i],fl[x][i]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa[x]) continue;dfs3(y);
for(int i=0;i<m;i++) f[x][i]=f[x][i]*(f[y][i]+1)%MOD;
for(int i=0;i<m;i++) sum[x][i]=(sum[x][i]+sum[y][i])%MOD;
} for(int i=0;i<m;i++) sum[x][i]=(sum[x][i]+f[x][i])%MOD;
}
void dfs4(int x=1){
if(wson[x]) dfs4(wson[x]);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa[x]||y==wson[x]) continue;dfs4(y);
for(int i=0;i<m;i++) fl[x][i]=fl[x][i]*((f[y][i]+1)%MOD);
for(int i=0;i<m;i++) suml[x][i]=(suml[x][i]+sum[y][i])%MOD;
}
}
struct mat{
poly a,b,c,d;
mat(){}
mat operator *(const mat &rhs){
mat res;res.a=a*rhs.a;res.b=b+a*rhs.b;
res.c=rhs.a*c+rhs.c;res.d=rhs.b*c+d+rhs.d;
return res;
}
};
void print(mat x){
for(int i=0;i<m;i++) printf("%d%c",x.a.a[i]," \n"[i==m-1]);
for(int i=0;i<m;i++) printf("%d%c",x.b.a[i]," \n"[i==m-1]);
for(int i=0;i<m;i++) printf("%d%c",x.c.a[i]," \n"[i==m-1]);
for(int i=0;i<m;i++) printf("%d%c",x.d.a[i]," \n"[i==m-1]);
}
mat get(int x){
mat res;
for(int i=0;i<m;i++) res.a.a[i]=res.b.a[i]=res.c.a[i]=fl[x][i].num()*e[val[x]].a[i]%MOD;
for(int i=0;i<m;i++) res.d.a[i]=(res.a.a[i]+suml[x][i])%MOD;
return res;
}
struct node{int l,r;mat v;} s[MAXN*4+5];
void pushup(int k){s[k].v=s[k<<1|1].v*s[k<<1].v;}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return s[k].v=get(rid[l]),void();
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
}
mat query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].v;
int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1|1,mid+1,r)*query(k<<1,l,mid);
}
void modify(int k,int p){
if(s[k].l==s[k].r) return s[k].v=get(rid[p]),void();
int mid=s[k].l+s[k].r>>1;
if(p<=mid) modify(k<<1,p);else modify(k<<1|1,p);
pushup(k);
}
void change(int x){
while(x){
if(fa[top[x]]){
mat res=query(1,dfn[top[x]],dfn[bot[top[x]]]);
// printf("%d\n",fa[top[x]]);
// for(int i=0;i<m;i++) printf("{%d,%d}%c",fl[fa[top[x]]][i].x,fl[fa[top[x]]][i].y," \n"[i==m-1]);
// for(int i=0;i<m;i++) printf("%d%c",(res.c.a[i]+1)%MOD," \n"[i==m-1]);
// print(get(fa[top[x]]));
for(int i=0;i<m;i++) fl[fa[top[x]]][i]=fl[fa[top[x]]][i]/((res.c.a[i]+1)%MOD);
for(int i=0;i<m;i++) suml[fa[top[x]]][i]=(suml[fa[top[x]]][i]-res.d.a[i]+MOD)%MOD;
} modify(1,dfn[x]);
if(fa[top[x]]){
mat res=query(1,dfn[top[x]],dfn[bot[top[x]]]);
// print(res);
for(int i=0;i<m;i++) fl[fa[top[x]]][i]=fl[fa[top[x]]][i]*((res.c.a[i]+1)%MOD);
for(int i=0;i<m;i++) suml[fa[top[x]]][i]=(suml[fa[top[x]]][i]+res.d.a[i])%MOD;
// for(int i=0;i<m;i++) printf("{%d,%d}%c",fl[fa[top[x]]][i].x,fl[fa[top[x]]][i].y," \n"[i==m-1]);
// for(int i=0;i<m;i++) printf("%d%c",(res.c.a[i]+1)%MOD," \n"[i==m-1]);
// print(get(fa[top[x]]));
} x=fa[top[x]];
}
}
int main(){
scanf("%d%d",&n,&m);getinv();
for(int i=0;i<m;i++) e[i].a[i]=1,e[i].FWT();
// for(int i=0;i<m;i++) for(int j=0;j<m;j++) printf("%d%c",e[i].a[j]," \n"[j==m-1]);
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
dfs1();dfs2();dfs3();dfs4();build(1,1,n);
// for(int i=1;i<=n;i++) for(int j=0;j<m;j++) printf("%d%c",f[i][j]," \n"[j==m-1]);
// for(int i=1;i<=n;i++) for(int j=0;j<m;j++) printf("%d%c",sum[i][j]," \n"[j==m-1]);
// for(int i=1;i<=n;i++) for(int j=0;j<m;j++) printf("%d%c",fl[i][j].num()," \n"[j==m-1]);
// for(int i=1;i<=n;i++) for(int j=0;j<m;j++) printf("%d%c",suml[i][j]," \n"[j==m-1]);
// for(int i=0;i<m;i++) printf("%d%c",t.d.a[i]," \n"[i==m-1]);
for(int i=1;i<=n;i++) if(top[i]==i){
int cur=i;while(wson[cur]) cur=wson[cur];
bot[i]=cur;
} int qu;scanf("%d",&qu);
while(qu--){
char opt[9];scanf("%s",opt+1);
if(opt[1]=='C'){
int x,v;scanf("%d%d",&x,&v);
val[x]=v;change(x);
} else {
int k;scanf("%d",&k);
mat res=query(1,dfn[1],dfn[bot[1]]);
// for(int i=0;i<m;i++) printf("%d%c",res.d.a[i]," \n"[i==m-1]);
res.d.IFWT();
printf("%d\n",res.d.a[k]);
}
}
return 0;
}

洛谷 P3781 - [SDOI2017]切树游戏(动态 DP+FWT)的更多相关文章

  1. 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)

    [BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...

  2. BZOJ4911: [Sdoi2017]切树游戏

    BZOJ 4911 切树游戏 重构了三次.jpg 每次都把这个问题想简单了.jpg 果然我还是太菜了.jpg 这种题的题解可以一眼秒掉了,FWT+动态DP简直是裸的一批... 那么接下来,考虑如何维护 ...

  3. LG3781 [SDOI2017]切树游戏

    题意 题目描述 小Q是一个热爱学习的人,他经常去维基百科学习计算机科学. 就在刚才,小Q认真地学习了一系列位运算符,其中按位异或的运算符\(\oplus\)对他影响很大.按位异或的运算符是双目运算符. ...

  4. [SDOI2017]切树游戏

    题目 二轮毒瘤题啊 辣鸡洛谷竟然有卡树剖的数据 还是\(loj\)可爱 首先这道题没有带修,设\(dp_{i,j}\)表示以\(i\)为最高点的连通块有多少个异或和为\(j\),\(g_{i,j}=\ ...

  5. LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】

    题目分析: 好题.本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法. 首先考虑静态的情况.这个的DP方程非常容易写出来. 接着可以注意到对于异或结果的计数可以看成一个FW ...

  6. 洛谷 P2059 [JLOI2013]卡牌游戏(概率dp)

    题面 洛谷 题解 \(f[i][j]\)表示有i个人参与游戏,从庄家(即1)数j个人获胜的概率是多少 \(f[1][1] = 1\) 这样就可以不用讨论淘汰了哪些人和顺序 枚举选庄家选那张牌, 枚举下 ...

  7. 【洛谷】P4643 【模板】动态dp

    题解 在冬令营上听到冬眠的东西,现在都是板子了猫锟真的是好毒瘤啊(雾) (立个flag,我去thusc之前要把WC2018T1乱搞过去= =) 好的,我们可以参考猫锟的动态动态dp的课件,然后你发现你 ...

  8. bzoj 4911: [Sdoi2017]切树游戏

    考虑维护原树的lct,在上面dp,由于dp方程特殊,均为异或卷积或加法,计算中可以只使用fwt后的序列 v[w]表示联通子树的最浅点为w,且不选w的splay子树中的点 l[w]表示联通子树的最浅点在 ...

  9. Solution -「洛谷 P4719」「模板」"动态 DP" & 动态树分治

    \(\mathcal{Description}\)   Link.   给定一棵 \(n\) 个结点的带权树,\(m\) 次单点点权修改,求出每次修改后的带权最大独立集.   \(n,m\le10^5 ...

随机推荐

  1. DataX的安装及使用

    DataX的安装及使用 目录 DataX的安装及使用 DataX的安装 DataX的使用 stream2stream 编写配置文件stream2stream.json 执行同步任务 执行结果 mysq ...

  2. [技术博客] 通过ItemTouchHelper实现侧滑删除功能

    通过ItemTouchHelper实现侧滑删除功能 一.效果 二.具体实现 demo中演示的这种左滑删除的效果在手机APP中比较常用,安卓也为我们提供了专门的辅助类ItemTouchHelper来帮助 ...

  3. IC封装的热特性

    ΘJA是结到周围环境的热阻,单位是°C/W.周围环境通常被看作热"地"点.ΘJA取决于IC封装.电路板.空气流通.辐射和系统特性,通常辐射的影响可以忽略.ΘJA专指自然条件下(没有 ...

  4. 从零开始 DIY 智能家居 - 基于 ESP32 的智能紫外线传感器模块

    目录 前言 硬件选择 二.使用步骤 获取代码 设备控制命令: 设备和协议初始化流程: 配置设备信息 回调函数注册 数据获取与上报流程 总结 前言 做了这么多传感器都是自己玩,这次家里人看不下去了,非得 ...

  5. PCIE笔记--PCIe错误定义与分类

    转载地址:http://blog.chinaaet.com/justlxy/p/5100057782 前面的文章提到过,PCI总线中定义两个边带信号(PERR#和SERR#)来处理总线错误.其中PER ...

  6. Filter学习笔记

    博客园的编辑器太丑了,所以我换用了别的Markdown编辑器,并用图片形式上传.

  7. SpringBoot2.x异步任务EnableAsync

    1.springboot启动类里面使用@EnableAsync注解开启异步功能 @EnableAsync public class Demo001Application { public static ...

  8. APP 自动化之手势操作appium提供API详解(四)

    一.手势操作1.上下左右滑屏 swipe---滑动 java-client 4.x 是有swipe方法的,可以通过传递坐标信息就可以完成滑动androidDriver.swipe(startx, st ...

  9. LeetCode 78. 子集 C++(位运算和回溯法)

    位运算 class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { ...

  10. WebSocket实现简易的FTP客户端

    WebScoket的简单应用,实现一个简易的FTP,即文件上传下载,可以查看上传人,下载次数,打开多个Web可以多人上传. 说在前面的话 文件传输协议(File Transfer Protocol,F ...