洛谷题面传送门

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. 什么,你还使用 webpack?别人都在用 vite 搭建项目了

    一.vite 到底是干嘛的? vite 实际上就是一个面向现代浏览器,基于 ES module 实现了一个更轻快的项目构建打包工具. vite 是法语中轻快的意思. vite 的特点: 1.轻快的冷服 ...

  2. Convolutional Neural Network-week2编程题2(Residual Networks)

    1. Residual Networks(残差网络) 残差网络 就是为了解决深网络的难以训练的问题的. In this assignment, you will: Implement the basi ...

  3. JVM:内存溢出OOM

    JVM:内存溢出OOM 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 经典错误 JVM 中常见的两个 OOM 错误 StackoverflowError:栈溢出 ...

  4. [敏捷软工团队博客]Beta阶段事后分析

    设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件要解决的问题是:现在的软工课程的作业分布在博客园.GitHub上,没有一个集成多种功能的一体化 ...

  5. UltraSoft - Alpha - 发布声明

    DDL_Killer Alpha版本发布声明 1. Alpha 阶段功能描述与版本实现 功能描述 设计原型 Alpha实现 登陆界面 注册界面 首页 日历视图 事项详情页 新建事项 列表视图 课程视图 ...

  6. Spring MVC:DispatchServlet类

    Spring MVC架构 Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中.传统的模型层被拆分为了业务层(Service)和数据访问层 ...

  7. Ubuntu用apt安装MySQL

    这里以MySQL5.7为例. # 如果之前有安装旧版,先移除sudo apt-get --purge remove mysql-server mysql-client mysql-common # 安 ...

  8. HTML bootstrap 模态对话框添加用户

    HTML 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> ...

  9. Appium 介绍与环境搭建

    目录 Appium 介绍 APP 自动化测试介绍 什么是 Appium ? Appium 优势 Appium 架构 Appium 生态 Appium 组件 UiAutomator API Bootst ...

  10. 攻防世界 WEB 高手进阶区 PHP2 Writeup

    攻防世界 WEB 高手进阶区 PHP2 Writeup 题目介绍 题目考点 url 二次解码 index.phps 文件(第一次使用dirsearch可能扫不到,需要加到工具字典里) php 简单语法 ...