洛谷题面传送门

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. 浏览器有别_HTTP报文的回车换行

    本来以为浏览器HTTP报文的生成应该是完全一致的.但最近在做一个项目的时候,发现Safari和Chrome提交同一份表单,后端的处理结果不一致.看提交结果呢,是因为Safari多了个回车.由于原项目的 ...

  2. 工厂模式--摆脱你日复一日new对象却依旧单身的苦恼!

    前言 每每谈及到Java,就不免会想到一个悲伤的事实:你是否每天都在new对象,却依然坚守在单身岗上屹立不倒.(所谓面向对象编程hhh),这篇来学一下工厂模式,摆脱new对象的苦恼! 知识点 传统工厂 ...

  3. 周末愉快--css画大熊猫

    周末找了点轻松的话题,css画大熊猫. 先上效果图 欢迎竞猜大熊猫到底说了什么?? 再上源码 <!DOCTYPE html> <html lang="en"> ...

  4. 普通用户在命令终端使用Python脚本连入校园网

    普通用户在命令终端使用Python脚本连入校园网 想要连入校园网的步骤: 浏览器输入对应的IP地址,输入账号密码连网: 下载对应软件,输入账号密码连网: 而面对没有界面的服务器,而你又没有root权限 ...

  5. [no code] Scrum Meeting 博客目录

    项目 内容 2020春季计算机学院软件工程(罗杰 任健) 2020春季计算机学院软件工程(罗杰 任健) 作业要求 Scrum Meeting博客目录 我们在这个课程的目标是 远程协同工作,采用最新技术 ...

  6. Prometheus基于Eureka的服务发现

    Prometheus基于Eureka的服务发现 一.背景 二.实现步骤 1.eureka 客户端注册到prometheus中 2.prometheus中的写法 3.实现效果 三.完整代码 四.参考链接 ...

  7. 阿里Nacos部署

    Nacos的部署 一.单机部署 **4.修改 Nacos 存储为 Mysql** 二.集群部署 1.机器部署列表 2.修改 `nacos/conf/application.properties`中的端 ...

  8. elf文件--基于《ctf竞赛权威指南pwn篇》

    1.ELF概念: ELF(Executable and Linkable Format),即"可执行可链接格式",最初由UNIX系统实验室作为应用程序二进制接口(Applicati ...

  9. 并发编程从零开始(十一)-Atomic类

    并发编程从零开始(十一)-Atomic类 7 Atomic类 7.1 AtomicInteger和AtomicLong 如下面代码所示,对于一个整数的加减操作,要保证线程安全,需要加锁,也就是加syn ...

  10. Jquery校验中国身份证号码是否正确

    在项目中使用表单时经常会涉及到身份证号码是否正确的校验,下面看看应该中国二代身份证号码应该怎么用Jquery校验呢? 二代身份证校验码的计算方法 二代身份证由17位数字和一位校验码组成,那么校验方法是 ...