动态dp(ddp)

模板题

在这里插入图片描述
动态修改节点权值求树上最大权点独立集。

首先考虑朴素dp:
f u , 0 / 1 f_{u,0/1} fu,0/1表示节点 u u u不选/选, u u u子树内最大权独立集的大小。
转移就是( v v v u u u的所有儿子):
{ f u , 0 = ∑ v max ⁡ { f v , 0 , f v , 1 } f u , 1 = h u + ∑ v f v , 0 \left\{\begin{matrix} f_{u,0}={\underset{v}\sum}\max\{f_{v,0},f_{v,1}\}\\ f_{u,1}=h_u+{\underset{v}\sum}f_{v,0}\hspace{0.95cm} \end{matrix}\right. fu,0=vmax{fv,0,fv,1}fu,1=hu+vfv,0

考虑到修改影响的节点是从节点 u u u到根节点 1 1 1的一条路径。

如果每次修改都暴力重新dp一下路径上的点理论上过不去。

考虑到一条到根的路径上只有 O ( log ⁡ n ) O(\log n) O(logn)条重链,换句话说路径上只有 O ( log ⁡ n ) O(\log n) O(logn)个节点是其父亲的轻儿子。

我们考虑树剖维护重儿子到父亲的转移,暴力轻儿子到父亲的转移。

首先是先对轻重儿子分治,老套路了:

g u , 0 / 1 g_{u,0/1} gu,0/1表示 u u u节点不选/选,只考虑 u u u的轻儿子所在的子树(假设重儿子所在子树不存在),最大权独立集的大小。

s o n u son_u sonu表示 u u u儿子,转移就是:
{ g u , 0 = ∑ v ≠ s o n u max ⁡ { f v , 0 , f v , 1 } g u , 1 = h u + ∑ v ≠ s o n u f v , 0 \left\{\begin{matrix} g_{u,0}={\underset{v\not= son_u}\sum}\max\{f_{v,0},f_{v,1}\} \\ g_{u,1}=h_u+{\underset{v\not= son_u}\sum}f_{v,0}\hspace{0.9cm} \end{matrix}\right. gu,0=v=sonumax{fv,0,fv,1}gu,1=hu+v=sonufv,0

同时,用 g g g可以还原出 f f f
{ f u , 0 = g u , 0 + m a x { f s o n u , 0 , f s o n u , 1 } f u , 1 = g u , 1 + f s o n u , 0 \left\{\begin{matrix} f_{u,0}=g_{u,0}+max\left\{f_{son_u,0},f_{son_u,1}\right\}\\ f_{u,1}=g_{u,1}+f_{son_u,0}\hspace{2.24cm} \end{matrix}\right. {fu,0=gu,0+max{fsonu,0,fsonu,1}fu,1=gu,1+fsonu,0

我们考虑如何快速修改重儿子。

把转移方程写成Floyd矩乘的形式:

定义矩阵广义矩阵乘法为:
A n , m ∗ B m , p = C n , p A_{n,m}*B_{m,p}=C_{n,p} An,mBm,p=Cn,p
c i , j = ⨁ k = 0 m a i , k ⊗ b k , j c_{i,j}=\overset{m}{\underset{k=0}{\bigoplus }}a_{i,k}\otimes b_{k,j} ci,j=k=0mai,kbk,j

在本题中 ⊕ \oplus max ⁡ \max max运算, ⊗ \otimes + + +运算,也就是 max ⁡ , + \max,+ max,+矩乘:
c i , j = max ⁡ k = 0 m { a i , k + b k , j } c_{i,j}=\overset{m}{\underset{k=0}{\max }}\left\{a_{i,k}+ b_{k,j}\right\} ci,j=k=0maxm{ai,k+bk,j}

我们要用线段树维护重链,因此要考虑如何用 f s o n u f_{son_u} fsonu推出 f u f_u fu

写成矩阵乘法就是:
X ∗ [ f s o n u , 0 f s o n u , 1 ] = [ f u , 0 f u , 1 ] X*\begin{bmatrix} f_{son_u,0}\\ f_{son_u,1} \end{bmatrix}=\begin{bmatrix} f_{u,0}\\ f_{u,1} \end{bmatrix} X[fsonu,0fsonu,1]=[fu,0fu,1]

这里用左乘系数矩阵,参照普通矩阵乘法的证明,显然这个矩阵乘法也符合结合律。

现在看看 X X X填什么。

首先知道:
f u , 0 = g u , 0 + max ⁡ { f s o n u , 0 , f s o n u , 1 } f_{u,0}=g_{u,0}+\max\left\{f_{son_u,0},f_{son_u,1}\right\} fu,0=gu,0+max{fsonu,0,fsonu,1}
把它搞成矩阵乘法的形式:
f u , 0 = max ⁡ { g u , 0 + f s o n u , 0 , g u , 0 + f s o n u , 1 } f_{u,0}=\max\left\{g_{u,0}+f_{son_u,0},g_{u,0}+f_{son_u,1}\right\} fu,0=max{gu,0+fsonu,0,gu,0+fsonu,1}

同理:
f u , 1 = g u , 1 + f s o n u , 0 f_{u,1}=g_{u,1}+f_{son_u,0}\hspace{2.24cm} fu,1=gu,1+fsonu,0

变成:
f u , 1 = max ⁡ { g u , 1 + f s o n u , 0 , − ∞ + f s o n u , 1 } f_{u,1}=\max\left\{g_{u,1}+f_{son_u,0},-\infty+f_{son_u,1}\right\} fu,1=max{gu,1+fsonu,0,+fsonu,1}

因此:
[ g u , 0 g u , 0 g u , 1 − ∞ ] [ f s o n u , 0 f s o n u , 1 ] = [ f u , 0 f u , 1 ] \begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty \end{bmatrix}\begin{bmatrix} f_{son_u,0}\\ f_{son_u,1} \end{bmatrix}=\begin{bmatrix} f_{u,0}\\ f_{u,1} \end{bmatrix} [gu,0gu,1gu,0][fsonu,0fsonu,1]=[fu,0fu,1]

那我们就可以先树剖一下,然后用线段树维护矩阵连乘积。
每次修改的时候,我们可以暴力修改 x x x位置的 g g g矩阵,进而得到 x x x所在重链链顶的新的 f f f矩阵,来更新 x x x链顶的父亲的 g g g矩阵,然后不断重复这个过程直到递归到根节点所在重链。(根节点所在重链的链顶没有父亲)
所以说我们实际上不储存 f f f值,只维护 g g g的矩阵连乘积。

话说这个矩阵乘法的单位矩阵是什么?
回想一下经典矩阵乘法( + , × +,\times +,×矩乘)的单位矩阵:
[ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} 100010001

我们会发现, 1 1 1 × \times ×的单位元, 0 0 0 + , × +,\times +,×的零元。

对于 max ⁡ , + \max,+ max,+矩乘:
我们会发现: 0 0 0 + + +的单位元, − ∞ -\infty max ⁡ , + \max,+ max,+的零元。
因此单位矩阵就是:
[ 0 − ∞ − ∞ − ∞ 0 − ∞ − ∞ − ∞ 0 ] \begin{bmatrix} 0 & -\infty & -\infty \\ -\infty & 0 & -\infty \\ -\infty & -\infty & 0 \end{bmatrix} 000

当然这道题只有2阶矩阵,因此单位矩阵其实是:

[ 0 − ∞ − ∞ 0 ] \begin{bmatrix} 0 & -\infty\\ -\infty & 0 \end{bmatrix} [00]

还是说一说代码:

int main() {
	int n,m;
	cin>>n>>m;
	for(int i=1; i<=n; i++) cin>>h[i];
	a.resize(n+1);
	for(int i=1,u,v; i<n; i++) {
		cin>>u>>v;
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs1(1);
	dfs2(1,1);前两遍dfs是树剖
	dfs3(1);求出初始的f和g
	build(1,1,n);初始化线段树
//	for(int i=1;i<=n;i++) 
//		cout<<i<<':'<<f[i][0]<<' '<<f[i][1]<<' '<<g[i][0]<<' '<<g[i][1]<<endl;
	for(int i=1;i<=n;i++) 
		push(1,dfn[i],dfn[i],{g[i][0],g[i][0],g[i][1],int(-1e9)});用一开始的g来建立线段树
	while(m--) {
		int u,x;
		cin>>u>>x;
		mat val=find(1,dfn[u],dfn[u]);获得u现在的矩阵
		val[1][0]-=h[u]-x;	撤销原来的h[u],把原来h[u]的位置变为x,修改矩阵
		h[u]=x;				更新h[u],以便下次修改矩阵
		pushx(u,val);		把新的u的矩阵放进去
		cout<<max(F(1)[0][0],F(1)[1][0])<<endl;
	}
}

然后主要是如何更新:

mat F(int u) {F(i)表示i的f矩阵,其实是一个列向量,但是为了方便,我们把列向量右边增加一列,扩展为2*2的矩阵。
	因此F(i)只有第一列的两个数字是有意义的。
	return find(1,dfn[u],dfn[bot[u]])*mat{0,int(-1e9),0,int(-1e9)};
	查询一个位置的F需要求出从它开始到它所在重链底端的矩阵连乘积,再右乘上重链底部节点的F矩阵
	但是重链底部节点一定是叶子,所以它的F矩阵一定是一个长度为2的全零列向量,把它扩展为一个二阶方阵。
	其实右边两个数填啥都行,这里习惯填为-1e9
}			
void pushx(int u,const mat&val) {
	函数pushx(u,val)表示把节点u的矩阵修改为val,同时递归修改上级重链的F值
	
	mat ago=F(top[u]);提前记录一下以前的链顶F值,方便修改
	push(1,dfn[u],dfn[u],val);
	if(!fa[top[u]]) return ;如果说链顶没有父亲,就结束
	
	否则还需要更新链顶父亲的g矩阵
	mat now=F(top[u]);获得链顶现有的f值
	mat x=find(1,dfn[fa[top[u]]],dfn[fa[top[u]]]);获得链顶父亲的g矩阵
	
	接下来三行去除原来的f[top[u]]的贡献,加上新的贡献:
	x[0][0]=x[0][0]-max(ago[0][0],ago[1][0])+max(now[0][0],now[1][0]);
	x[0][1]=x[0][0];
	x[1][0]=x[1][0]-ago[0][0]+now[0][0];
	pushx(fa[top[u]],x)递归调用函数修改链顶的父亲;
}

还有一个小细节就是,矩阵乘法没有交换律,因此在push_up的时候以及计算贡献的时候需要注意乘法的方向。
左乘系数矩阵就是先乘左边再乘右边。

完整代码:

#include<iostream>
#include<vector>
using namespace std;
const int N=1e5;
vector<vector<int>> a;
int dep[N+5],siz[N+5],top[N+5],bot[N+5],son[N+5],dfn[N+5],fa[N+5],tot;
int dfs1(int u) {
	siz[u]=1;
	dep[u]=dep[fa[u]]+1;
	for(auto&v:a[u])
		if(v^fa[u]) {
			fa[v]=u;
			siz[u]+=dfs1(v);
			if(siz[son[u]]<siz[v]) son[u]=v;
		}
	return siz[u];
}
int dfs2(int u,int t) {
	dfn[u]=++tot;
	top[u]=t;
	bot[u]=son[u]?dfs2(son[u],t):u;
	for(auto&v:a[u])
		if(v^fa[u]&&v^son[u])
			dfs2(v,v);
	return bot[u];
}
int h[N+5];
class mat {
	public:
		int a[2][2];
		mat() {
			a[0][0]=a[1][1]=0;
			a[0][1]=a[1][0]=-1e9;
		}
		int*operator[](int x) {
			return a[x];
		}
		mat(int x,int y,int z,int w) {
			a[0][0]=x;
			a[0][1]=y;
			a[1][0]=z;
			a[1][1]=w;
		}
		mat(const mat&x) {
			for(int i=0; i<2; i++) for(int j=0; j<2; j++) a[i][j]=x.a[i][j];
		}
		mat&operator=(const mat& x) {
			for(int i=0; i<2; i++) for(int j=0; j<2; j++) a[i][j]=x.a[i][j];
			return *this;
		}
		friend mat operator*(const mat&,const mat&);
};
mat operator*(const mat&a,const mat&b) {
	mat c={(int)-1e9,(int)-1e9,(int)-1e9,(int)-1e9};注意初值设置
//	mat c;
	for(int i=0; i<2; i++) 
		for(int j=0; j<2; j++) 
			for(int k=0;k<2;k++) 
				c[i][j]=max(c[i][j],a.a[i][k]+b.a[k][j]);
	return c;
}
struct node {
	int l,r;
	mat val;
} t[N<<2];
void build(int u,int l,int r) {
	t[u]= {l,r};
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
}
void push_up(int u) {
	t[u].val=t[u<<1].val*t[u<<1|1].val;
}
void push_down(int u) {

}
void push(int u,int l,int r,const mat& val) {
	if(l<=t[u].l&&t[u].r<=r) t[u].val=val;
	else {
		int mid=t[u].l+t[u].r>>1;
		if(l<=mid) push(u<<1,l,r,val);
		if(mid<r) push(u<<1|1,l,r,val);
		push_up(u);
	}
}
mat find(int u,int l,int r) {
	if(l<=t[u].l&&t[u].r<=r) return t[u].val;
	int mid=t[u].l+t[u].r>>1;
	mat ans;
	if(l<=mid) ans=ans*find(u<<1,l,r);
	if(mid<r) ans=ans*find(u<<1|1,l,r);
	return ans;
}
int f[N+5][2],g[N+5][2];
void dfs3(int u) {
	g[u][1]=h[u];
	for(auto&v:a[u]) 
		if(v^fa[u]) {
			dfs3(v);
			if(son[u]^v) g[u][0]+=max(f[v][0],f[v][1]),g[u][1]+=f[v][0];
		}
	f[u][0]=g[u][0]+max(f[son[u]][0],f[son[u]][1]);
	f[u][1]=g[u][1]+f[son[u]][0];
}
mat F(int u) {
	return find(1,dfn[u],dfn[bot[u]])*mat{0,int(-1e9),0,int(-1e9)};
}
void pushx(int u,const mat&val) {
	mat ago=F(top[u]);
	push(1,dfn[u],dfn[u],val);
	if(!fa[top[u]]) return ;
	mat now=F(top[u]);
	mat x=find(1,dfn[fa[top[u]]],dfn[fa[top[u]]]);
	x[0][0]=x[0][0]-max(ago[0][0],ago[1][0])+max(now[0][0],now[1][0]);
	x[0][1]=x[0][0];
	x[1][0]=x[1][0]-ago[0][0]+now[0][0];
	pushx(fa[top[u]],x);
}
int main() {
	int n,m;
	cin>>n>>m;
	for(int i=1; i<=n; i++) cin>>h[i];
	a.resize(n+1);
	for(int i=1,u,v; i<n; i++) {
		cin>>u>>v;
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs1(1);
	dfs2(1,1);
	dfs3(1);
	build(1,1,n);
//	for(int i=1;i<=n;i++) 
//		cout<<i<<':'<<f[i][0]<<' '<<f[i][1]<<' '<<g[i][0]<<' '<<g[i][1]<<endl;
	for(int i=1;i<=n;i++) 
		push(1,dfn[i],dfn[i],{g[i][0],g[i][0],g[i][1],int(-1e9)});
	while(m--) {
		int u,x;
		cin>>u>>x;
		mat val=find(1,dfn[u],dfn[u]);
		val[1][0]-=h[u]-x;
		h[u]=x;
		pushx(u,val);
		cout<<max(F(1)[0][0],F(1)[1][0])<<endl;
	}
}
/*
几组hack:

3 10000
1 2 4
1 2
1 3
1 1000

3 10000
1 2 4
1 2
1 3
3 0

5 10000
1 2 4 8 16
1 2
1 5
2 3 
2 4

*/

模板题2

动态修改节点权值求树上最小权点覆盖集

因为如果选的话就相当于强制不选的费用为 + ∞ +\infty +,不选就相当于强制选的费用为 + ∞ +\infty +
这挺好理解的。
怎么强制不选的费用呢?可以理解为给这个节点下面连一个权值为 + ∞ +\infty +的儿子。
当然实现不需要这么麻烦。

f u , 0 / 1 f_{u,0/ 1} fu,0/1表示节点 u u u不选/选的贡献,则:
{ f u , 0 = ∑ v f v , 1 f u , 1 = h u + ∑ v min ⁡ { f v , 0 , f v , 1 } \left\{\begin{matrix} f_{u,0}=\underset{v}{\sum}f_{v,1}\hspace{2.5cm}\\ f_{u,1}=h_u+\underset{v}{\sum}\min \{f_{v,0},f_{v,1}\} \end{matrix}\right. fu,0=vfv,1fu,1=hu+vmin{fv,0,fv,1}

同理对轻重儿子分治,设 g u g_u gu表示只考虑轻儿子的子树,节点 u u u不选/选的的贡献:
{ g u , 0 = ∑ v ≠ s o n u f v , 1 g u , 1 = h u + ∑ v ≠ s o n u min ⁡ { f v , 0 , f v , 1 } \left\{\begin{matrix} g_{u,0}=\underset{v\not =son_u}{\sum}f_{v,1}\hspace{2.6cm}\\ g_{u,1}=h_u+\underset{v\not=son_u}{\sum}\min \{f_{v,0},f_{v,1}\} \end{matrix}\right. gu,0=v=sonufv,1gu,1=hu+v=sonumin{fv,0,fv,1}

然后写出从 g g g f f f的转移:

{ f u , 0 = g u , 0 + f s o n u , 1 f u , 1 = g u , 1 + min ⁡ { f s o n u , 0 , f s o n u , 1 } \left\{\begin{matrix} f_{u,0}=g_{u,0}+f_{son_u,1}\hspace{2.1cm}\\ f_{u,1}=g_{u,1}+\min\{f_{son_u,0},f_{son_u,1}\}\end{matrix}\right. {fu,0=gu,0+fsonu,1fu,1=gu,1+min{fsonu,0,fsonu,1}

最后写出 min ⁡ , + \min,+ min,+矩乘的转移:
[ + ∞ g u , 0 g u , 1 g u , 1 ] [ f s o n u , 0 f s o n u , 1 ] = [ f u , 0 f u , 1 ] \begin{bmatrix} +\infty & g_{u,0}\\ g_{u,1} & g_{u,1} \end{bmatrix}\begin{bmatrix} f_{son_u,0}\\ f_{son_u,1} \end{bmatrix}=\begin{bmatrix} f_{u,0}\\ f_{u,1} \end{bmatrix} [+gu,1gu,0gu,1][fsonu,0fsonu,1]=[fu,0fu,1]

于是可以树剖来维护。

注意把输出-1的情况给判掉就可以了。显然只有直接父子之间都不能选时才输出-1。

#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
const int N=1e5;
vector<vector<int>> a;
long long b[N+5];
int dep[N+5],siz[N+5],top[N+5],dfn[N+5],fa[N+5],bot[N+5],son[N+5],tot;
int dfs1(int u) {
	dep[u]=dep[fa[u]]+1;
	siz[u]=1;
	for(auto&v:a[u])
		if(v^fa[u]) {
			fa[v]=u;
			siz[u]+=dfs1(v);
			if(siz[son[u]]<siz[v]) son[u]=v;
		}
	return siz[u];
}
int dfs2(int u,int t) {
	dfn[u]=++tot;
	top[u]=t;
	bot[u]=son[u]?dfs2(son[u],t):u;
	for(auto&v:a[u])
		if(v^fa[u]&&v^son[u])
			dfs2(v,v);
	return bot[u];
}
class mat {
	public:
		long long a[2][2];
		mat() {
			a[0][0]=a[1][1]=0,a[1][0]=a[0][1]=1e18;
		}
		mat(long long x,long long y,long long z,long long w) {
			a[0][0]=x;
			a[0][1]=y;
			a[1][0]=z;
			a[1][1]=w;
		}
		long long*operator[](int x) {
			return a[x];
		}
		mat(const mat&b) {
			for(int i=0; i<2; i++)
				for(int j=0; j<2; j++)
					a[i][j]=b.a[i][j];
		}
		mat operator=(const mat &b) {
			for(int i=0; i<2; i++)
				for(int j=0; j<2; j++)
					a[i][j]=b.a[i][j];
			return *this;
		}
		mat operator*(mat b) {
			mat c= {(long long)1e16,(long long)1e16,(long long)1e16,(long long)1e16};
			for(int i=0; i<2; i++)
				for(int j=0; j<2; j++)
					for(int k=0; k<2; k++)
						c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
			return c;
		}
};
struct node {
	int l,r;
	mat val;
} t[N<<2];
void build(int u,int l,int r) {
	t[u]= {l,r};
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
}
void push_up(int u) {
	t[u].val=t[u<<1].val*t[u<<1|1].val;
}
void push(int u,int l,int r,mat& val) {
	if(l<=t[u].l&&t[u].r<=r) t[u].val=val;
	else {
		int mid=t[u].l+t[u].r>>1;
		if(l<=mid) push(u<<1,l,r,val);
		if(mid<r) push(u<<1|1,l,r,val);
		push_up(u);
	}
}
mat find(int u,int l,int r) {
	if(l<=t[u].l&&t[u].r<=r) return t[u].val;
	int mid=t[u].l+t[u].r>>1;
	mat ans;
	if(l<=mid) ans=ans*find(u<<1,l,r);
	if(mid<r) ans=ans*find(u<<1|1,l,r);
	return ans;
}
long long f[N+5][2],g[N+5][2];
void dfs3(int u) {
	g[u][1]=b[u];
	for(auto&v:a[u])
		if(v^fa[u]) {
			dfs3(v);
			if(v^son[u])
				g[u][0]+=f[v][1],
				         g[u][1]+=min(f[v][0],f[v][1]);
		}
	f[u][0]=g[u][0]+f[son[u]][1];
	f[u][1]=g[u][1]+min(f[son[u]][0],f[son[u]][1]);
}
mat F(int x) {
	return find(1,dfn[x],dfn[bot[x]])*mat {0,(long long)1e16,(long long)0,(long long)1e16};
}
void pushx(int u,mat&val) {
	mat ago=F(top[u]);
	push(1,dfn[u],dfn[u],val);
	mat now=F(top[u]);
	if(!fa[top[u]]) return;
	mat x=find(1,dfn[fa[top[u]]],dfn[fa[top[u]]]);
	x[0][1]=x[0][1]-ago[1][0]+now[1][0];
	x[1][0]=x[1][0]-min(ago[0][0],ago[1][0])+min(now[0][0],now[1][0]);
	x[1][1]=x[1][0];
	pushx(fa[top[u]],x);
}
//void print(int u) {
//	printf("%d:[%d,%d]:\n%lld %lld\n%lld %lld\n\n",u,t[u].l,t[u].r,t[u].val[0][0],t[u].val[0][1],t[u].val[1][0],t[u].val[1][1]);
//}
//void check(int u) {
//	print(u);
//	if(t[u].l==t[u].r) return ;
//	check(u<<1);
//	check(u<<1|1);
//}
int main() {
	int n,m;
	string s;
	cin>>n>>m>>s;
	a.resize(n+1);
	for(int i=1; i<=n; i++) cin>>b[i];
	for(int i=1,u,v; i<n; i++) {
		cin>>u>>v;
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs1(1);
	dfs2(1,1);
	dfs3(1);
	build(1,1,n);
	for(int i=1; i<=n; i++) {
		mat t= {(long long)1e16,g[i][0],g[i][1],g[i][1]};
		push(1,dfn[i],dfn[i],t);
	}

	while(m--) {
		int x,y;
		bool p,q;
		cin>>x>>p>>y>>q;
		if(dep[x]>dep[y]) swap(x,y),swap(p,q);
		这是为了确保要么x是y的祖先,要么x,y不在一个子树上。
		因为我们修改两个节点时使用了取巧的方法。
		我们加入的时候记录一下原来x的矩阵和原来y的矩阵,然后算出修改的矩阵。
		然后我们先把x的新矩阵加入,再把y的新矩阵加入。
		删除的时候先把y原来的加入,再把x原来的矩阵加入。

		因为修改子孙可能会对祖先的矩阵产生影响。
	假如我们先加入修改过后的y矩阵(yt),此时设受到影响的x矩阵变为t,再加入修改过后的x矩阵(xt)。
	但是xt是在xv的基础上计算出来的,不是在t的基础上计算出来的,就可能导致错误。
	
		if(!p&&!q&&(fa[x]==y||fa[y]==x)) {
			cout<<-1<<endl;
			continue;
		}
		mat xv=find(1,dfn[x],dfn[x]),yv=find(1,dfn[y],dfn[y]),
		    xt=p?mat {(long long)1e16,(long long)1e14,xv[1][0],xv[1][1]}
		       :
		       mat {(long long)1e16,xv[0][1],(long long)1e14,(long long)1e14},
		       yt=q?mat {(long long)1e16,(long long)1e14,yv[1][0],yv[1][1]}
		          :
		          mat {(long long)1e16,yv[0][1],(long long)1e14,(long long)1e14};
		pushx(x,xt);注意加入的顺序
		pushx(y,yt);

		mat ans=F(1);
		cout<<min(ans[0][0],ans[1][0])<<endl;
		pushx(y,yv);注意撤销的顺序
		pushx(x,xv);

	}
}
/*
3 10000 X
1 2 4
1 2
1 3


4 100000 X
1 2 4 8
1 2
1 3
2 4
1 1 3 1
1 1 4 1


*/

[PKUWC2018] Minimax

分析一下会发现这个式子完全不能处理,所以说应该要求出每个值在根节点的概率。

首先考虑一个比较裸的思路,设 f u , i f_{u,i} fu,i表示 u u u节点取到排名为 i i i的数的概率,则可以有转移(如果 u u u有两个儿子):
f u , i = f l u , i ( p u ∑ j = 1 i − 1 f r u , j + ( 1 − p u ) ∑ j = i + 1 m f r u , j ) + f r u , i ( p u ∑ j = 1 i − 1 f l u , j + ( 1 − p u ) ∑ j = i + 1 m f l u , j ) f_{u,i}=f_{l_u,i}\left(p_u\overset{ i-1}{\underset{j=1}\sum}f_{r_u,j}+(1-p_u)\overset{ m}{\underset{j=i+1}\sum}f_{r_u,j}\right)+f_{r_u,i}\left(p_u\overset{ i-1}{\underset{j=1}\sum}f_{l_u,j}+(1-p_u)\overset{ m}{\underset{j=i+1}\sum}f_{l_u,j}\right) fu,i=flu,i(puj=1i1fru,j+(1pu)j=i+1mfru,j)+fru,i(puj=1i1flu,j+(1pu)j=i+1mflu,j)

线段树合并可以做这个,因为线段树合并只需要处理当前节点只有一方有节点时可以快速合并即可,而如果只有一方在当前节点内有节点的话,转移是平凡的。
否则递归即可,把递归到节点 u u u时节点外的前缀后缀和预处理出来即可。

但是我们讨论的是ddp的做法,因此要复杂一些。

首先可以注意到如果我们把叶子结点权值从大到小放进去对应的叶子,假设我们刚刚放入的是 p x p_x px,设 s u s_u su表示 u u u节点目前有值的概率(换句话说是 u u u节点的值 ≥ p x \geq p_x px的概率),初始所有 s u = 0 s_u=0 su=0
则我们可以算出 s f a u s_{fa_u} sfau,进而算出 s f a f a u s_{fa_{fa_u}} sfafau,直到 s 1 s_1 s1

  • u u u不是 x x x的祖先: s u = s u s_u=s_u su=su
  • u = x u=x u=x s u = 1 s_u=1 su=1
  • u u u x x x的祖先(转移从叶子到根):
    X X X表示 u u u朝向 x x x方向的儿子, Y Y Y表示另一方向的儿子:
    • Y ≠ ∅ Y\not=\varnothing Y=
      s u = p u ⋅ s X + p u ⋅ s Y + ( 1 − 2 ⋅ p u ) s X s Y s_u=p_u\cdot s_X+p_u\cdot s_Y+(1-2\cdot p_u)s_Xs_Y su=pusX+pusY+(12pu)sXsY
      这是因为对于选到较大值的情况,有 p u p_u pu的概率,其中 X X X有值的概率是 s X s_X sX Y Y Y有值的概率是 s Y s_Y sY,还有 s X s Y s_Xs_Y sXsY的概率 X , Y X,Y X,Y都有值,算重了要减去。
      对于选到较小值的情况,有 1 − p u 1-p_u 1pu的概率,此时只有 X , Y X,Y X,Y都有值是合法的,再加上概率 s X s Y s_Xs_Y sXsY
    • Y = ∅ Y=\varnothing Y=
      s u = s X s_u=s_X su=sX

这个东西是可以动态dp的,具体来说,假设本次最后一个dp值被更新的点是 s o n u son_u sonu,则:

  • u u u有两个儿子,设 g u g_u gu表示 u u u轻儿子的 s s s值:
    [ p u + g u ( 1 − 2 ⋅ p u ) p u ⋅ g u 0 1 ] [ s s o n 1 ] = [ s u 1 ] \begin{bmatrix} p_u+g_u(1-2\cdot p_u) & p_u\cdot g_u\\ 0 & 1 \end{bmatrix}\begin{bmatrix} s_{son} \\ 1 & \end{bmatrix} =\begin{bmatrix} s_u \\ 1 \end{bmatrix} [pu+gu(12pu)0pugu1][sson1]=[su1]
  • 如果 u u u只有一个儿子,则:
    [ 1 0 0 1 ] [ s s o n 1 ] = [ s u 1 ] \begin{bmatrix} 1&0\\ 0 & 1 \end{bmatrix}\begin{bmatrix} s_{son} \\ 1 & \end{bmatrix} =\begin{bmatrix} s_u \\ 1 \end{bmatrix} [1001][sson1]=[su1]

所以说我们跳重链,一条重链上只需要求转移矩阵连乘积,就可以知道链顶的 s s s,然后再在链顶使用转移方程跳轻边,就又到了新的一条重链。

然后就可以做了,需要注意的是,这一次叶节点是没有转移矩阵的,而列向量的初值是: [ s u = p u ≤ p x 1 ] \begin{bmatrix} s_u=p_u\leq p_x\\ 1 \end{bmatrix} [su=pupx1],所以说这一次获得 s u s_u su的方法是取 u u u到所在重链底父亲的转移矩阵,乘上重链底节点对应的列向量。

注意到动态dp肯定是不可能在线段树里直接维护对应节点的dp数组的,因为这样的话更新一个节点一定会导致它到根节点链上的dp值全部需要更新一次。

时间复杂度 O ( n log ⁡ 2 n T 3 ) O(n\log^2 nT^3) O(nlog2nT3),其中 T = 2 T=2 T=2,需要一定的卡常才能通过。全局平衡二叉树应该可以轻松通过。或者注意到转移矩阵只有上面两个值是变量,即说明 s u s_u su s s o n s_son sson的一次函数,维护一次函数半群即可。

代码实现:

#include<bits/stdc++.h>
#include<vector>
#include<array>
using namespace std;
constexpr int N=3e5;
constexpr long long M=998244353;
class mat {
	public:
		long long a[2][2];
		mat(long long a00,long long a01,long long a10,long long a11) {
			a[0][0]=a00;
			a[0][1]=a01;
			a[1][0]=a10;
			a[1][1]=a11;
		}
		mat():mat(1,0,0,1) {
		}
		const long long* operator[](int x)const {
			return a[x];
		}
		long long*operator[](int x) {
			return a[x];
		}
		mat(const mat&b) {
			a[0][0]=b[0][0];
			a[0][1]=b[0][1];
			a[1][0]=b[1][0];
			a[1][1]=b[1][1];
		}
		mat&operator=(const mat&b) {
			a[0][0]=b[0][0];
			a[0][1]=b[0][1];
			a[1][0]=b[1][0];
			a[1][1]=b[1][1];
			return *this;
		}
		mat operator*(const mat&b) {
			mat c;
			for(int i=0; i<2; i++)
				for(int j=0; j<2; j++)
						(c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j])%=M;
			return c;
		}
};

vector<int> a[N+5];
int dfn[N+5],dep[N+5],fa[N+5],siz[N+5],son[N+5],top[N+5],bot[N+5],cnt;
int dfs1(int u) {
	dep[u]=dep[fa[u]]+1;
	siz[u]=1;
	for(auto&v:a[u]) {
		siz[u]+=dfs1(v);
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
	return siz[u];
}
int dfs2(int u,int t) {
	dfn[u]=++cnt;
	top[u]=t;
	bot[u]=son[u]?dfs2(son[u],t):u;
	for(auto&v:a[u])
		if(v^son[u])
			dfs2(v,v);
	return bot[u];
}

long long p[N+5];
struct node {
	int l,r;
	mat val;
} t[N<<2];
void push_up(int u) {
	t[u].val=t[u<<1].val*t[u<<1|1].val;
}
void build(int u,int l,int r) {
	t[u]= {l,r};
	if(l==r) return ;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
}
void push(int u,int l,int r,const mat&val) {
	if(l<=t[u].l&&t[u].r<=r) return t[u].val=val,void();
	int mid=t[u].l+t[u].r>>1;
	if(l<=mid) push(u<<1,l,r,val);
	if(mid<r) push(u<<1|1,l,r,val);
	push_up(u);
}
mat find(int u,int l,int r) {
	if(l<=t[u].l&&t[u].r<=r) return t[u].val;
	int mid=t[u].l+t[u].r>>1;
	mat ans;
	if(l<=mid) ans=ans*find(u<<1,l,r);
	if(mid<r) ans=ans*find(u<<1|1,l,r);
	return ans;
}

bool s[N+5];
mat S(int u) {
	return find(1,dfn[u],dfn[bot[u]])*mat {s[bot[u]],0,1,0};
}
void pushx(int u,mat val) {
	push(1,dfn[u],dfn[u],val);
	if(!fa[top[u]]) return ;
	long long g=S(top[u])[0][0];
	int x=fa[top[u]];
	pushx(x, {(p[x]+(1+(M-1)*2%M*p[x]%M)*g)%M,p[x]*g%M,0,1});
}
int main() {
	int n;
	cin>>n;
	for(int i=1; i<=n; i++) scanf("%d",&fa[i]),a[fa[i]].push_back(i);
	for(int i=1; i<=n; i++) scanf("%d",&p[i]);
	dfs1(1);
	dfs2(1,1);
	vector<int> x;
	for(int i=1; i<=n; i++) if(!son[i]) x.push_back(i);
		else p[i]=p[i]*796898467%M;
	build(1,1,n);
	for(int i=1;i<=n;i++)
		if(a[i].size()==2)
			push(1,dfn[i],dfn[i],{p[i],0,0,1});
		else 
			push(1,dfn[i],dfn[i],{1,0,0,1});
			
	sort(x.begin(),x.end(),[](int a,int b)->int{return p[a]<p[b];});
	long long ans=0;
	for(int i=x.size()-1; i+1; i--) {
		long long lst=S(1)[0][0];
		s[x[i]]=1;
		pushx(x[i], {1,0,0,1});
		long long d=(S(1)[0][0]+(M-1)*lst)%M;
		(ans+=(i+1)*p[x[i]]%M*d%M*d%M)%=M;
	}
	cout<<ans;
}
/* 
3
0 1 1
9212 2743 2175
652340104

4
0 1 1 1 
3685 5961 2447 3243 

6
0 1 1 2 2 3 
5000 5000 5000 1 2 3
436731907
*/

后记

最大权独立集+最小权覆盖集=全集。

于是皆大欢喜。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值