[SHOI2014]三叉神经树 题解

一道 ddp 好题

首先假设节点 \(u\) 的三个儿子 \(a,b,c\)

1 的权值占多数的充要条件为存在两个儿子的权值都为 1

那么可列出方程

\[p_u=(p_a \land p_b) \lor (p_a \land p_c) \lor (p_b \land p_c) \]

(感谢 wangrx 学长提供的思路)

一个广义矩阵乘法满足结合律的条件是内层运算有交换律结合律,内层运算对外层运算有分配律

注意到 \(\land,\lor\) 都有交换律,互成分配律

此处假设 \(a\) 为重儿子,根据分配律逆算,可以提出一个 \(p_a\) ,有:

\[p_u=[p_a \land (p_b \lor p_c)]\lor (p_b \land p_c) \]

写成矩阵形式,定义广义矩阵乘法为:

\[a_{i,j}=\bigvee_{k=1}^n b_{i,k} \land c_{k,j} \]

那么有:

\[\begin{bmatrix} p_b \lor p_c&p_b \land p_c\\ 0&1 \end{bmatrix} \begin{bmatrix} p_a\\ 1 \end{bmatrix} = \begin{bmatrix} p_u\\ 1 \end{bmatrix} \]

注意当无儿子节点是应乘上单位阵

\[I=\begin{bmatrix} 1&0\\ 0&1 \end{bmatrix} \]

接下来是 ddp 的套路,把每条重链的轻儿子的所有 dp 值求出来后得到整体的转移矩阵,再进行转移

为了复杂度不那么 naive ,我采用了全局平衡二叉树,将每条重链按深度重构成一颗 bst ,再按 LCT 的方式连接起来,注意重构时为了保证深度,每次以轻子树大小为权,二分链的带权重心作为根,递归建树,深度 \(O(\log n)\)

维护 bst 上一颗子树的矩阵连乘积 \(sm\),修改时仅用修改当前节点到根的路径上的 \(sm\) 矩阵和经过的重链链顶节点的 dp 值即可,总时间复杂度 \(O(n \log n)\)

就算有矩阵的大常数,也轻松跑进第一版

注意一点,链顶节点不一定是 bst 的根,因此需要开个数组记录

个人认为 ddp 相对 LCT 好想很多(而且我觉得可能比 LCT 容易写一些,因为代码虽然看起来长,实际上矩阵和快读占了多数)

Code:

#include <bits/stdc++.h>
#define getchar() (ipos==iend and (iend=(ipos=_ibuf)+fread(_ibuf,1,__bufsize,stdin),ipos==iend)?EOF:*ipos++)
#define putchar(ch) (opos==oend?fwrite(_obuf,1,__bufsize,stdout),opos=_obuf:0,*opos++=(ch))
#define __bufsize (1<<20)
using namespace std;
char _ibuf[__bufsize],_obuf[__bufsize],_stk[20];
char *ipos=_ibuf,*iend=_ibuf,*opos=_obuf,*oend=_obuf+__bufsize,*stkpos=_stk;
struct END{~END(){fwrite(_obuf,1,opos-_obuf,stdout);}}__;
inline int read(){
    int x=0,ch;
    for(;!isdigit(ch);ch=getchar());
    for(x=0;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
    return x;
}
struct mat{bool s[2][2];mat(){s[1][1]=s[0][0]=1;s[1][0]=s[0][1]=0;}};
mat operator *(const mat x,const mat y){
	mat t;
	t.s[0][0]=(x.s[0][0] and y.s[0][0]) or (x.s[0][1] and y.s[1][0]);
	t.s[0][1]=(x.s[0][0] and y.s[0][1]) or (x.s[0][1] and y.s[1][1]);
	t.s[1][0]=(x.s[1][0] and y.s[0][0]) or (x.s[1][1] and y.s[1][0]);
	t.s[1][1]=(x.s[1][0] and y.s[0][1]) or (x.s[1][1] and y.s[1][1]);
	return t;
}
void print(const mat x){
	printf("%d %d\n",x.s[0][0],x.s[0][1]);
	printf("%d %d\n",x.s[1][0],x.s[1][1]);
} 
const int _=3000003;
int pa[_],pb[_],pc[_],sz[_]; 
int fa[_],lc[_],rc[_],up[_];
bool pu[_]; 
mat vl[_],sm[_];
int n,q,rt;
void dfs(int u){
	sz[u]=1;
	if (u>n){
		vl[u].s[0][0]=pu[u];
		vl[u].s[0][1]=0;
		vl[u].s[1][0]=1;
		vl[u].s[1][1]=0; //叶子结点应存储初始矩阵
		return;
	}
	dfs(pa[u]); sz[u]+=sz[pa[u]];
	dfs(pb[u]); sz[u]+=sz[pb[u]];
	dfs(pc[u]); sz[u]+=sz[pc[u]];
	if (sz[pb[u]]>sz[pa[u]]) swap(pa[u],pb[u]);
	if (sz[pc[u]]>sz[pa[u]]) swap(pa[u],pc[u]); //注意 a 为重儿子,保证复杂度
	pu[u]=(pu[pa[u]]&&pu[pb[u]])||(pu[pa[u]]&&pu[pc[u]])||(pu[pb[u]]&&pu[pc[u]]);
	vl[u].s[0][0]=pu[pb[u]]||pu[pc[u]];
	vl[u].s[0][1]=pu[pb[u]]&&pu[pc[u]];
	vl[u].s[1][0]=0;
	vl[u].s[1][1]=1;
}
int stk[_],tp;
int tr(int L,int R){
	if (L>R) return 0;
	int l=L,r=R;
	while (l<r){
		int t=(l+r)>>1;
		if (2*sz[stk[t+1]]<=sz[stk[L]]+sz[stk[R+1]]) r=t;
		else l=t+1; //轻子树大小等于子树大小减去重子树大小,因此加减相消以后可以得到二分平衡条件
	}
	int k=stk[l];
	lc[k]=tr(L,l-1); rc[k]=tr(l+1,R);
	if (lc[k]) fa[lc[k]]=k;
	if (rc[k]) fa[rc[k]]=k;
	sm[k]=sm[lc[k]]*vl[k]*sm[rc[k]];
	return k;
}
int bld(int u){
	for (int i=u; i<=n; i=pa[i]){
		fa[bld(pb[i])]=i;
		fa[bld(pc[i])]=i;
	}
	for (int i=u; i; i=pa[i]) stk[++tp]=i,up[i]=u;
	int t=tr(1,tp);
	while (tp) stk[tp--]=0; //注意弹栈
	return t; 
}
void upd(int u){
	vl[u].s[0][0]^=1;
	while (u){
		sm[u]=sm[lc[u]]*vl[u]*sm[rc[u]];
		if (lc[fa[u]]!=u&&rc[fa[u]]!=u){
			pu[up[u]]=sm[u].s[0][0]; //修改链顶p值
			if (fa[u]){
				vl[fa[u]].s[0][0]=pu[pb[fa[u]]]||pu[pc[fa[u]]];
				vl[fa[u]].s[0][1]=pu[pb[fa[u]]]&&pu[pc[fa[u]]]; 
			}
		}
		u=fa[u];
	}
}
int main(){
	n=read();
	for (int i=1; i<=n; i++) pa[i]=read(),pb[i]=read(),pc[i]=read(); 
	for (int i=n+1; i<=3*n+1; i++) pu[i]=read();
	dfs(1); rt=bld(1);
	q=read();
	while (q--){
		int u=read();
		upd(u);
		if (sm[rt].s[0][0]) putchar('1');
		else putchar('0');
		putchar('\n');
	}
	return 0;
}
posted @ 2021-11-19 08:42  yyyyxh  阅读(40)  评论(0编辑  收藏  举报