动态DP小记
command_block
2020-06-08 14:43:37
最近你可能在本`Blog`看到许多这样的小记,这完全属于省选之前的游击战术,内容不保证严谨,也不保证大多数人能看懂。
如果省选过后有时间我会来仔细更新并且把这些文字去掉的。
------------
- [P4719 【模板】"动态 DP"&动态树分治](https://www.luogu.com.cn/problem/P4719)
首先要得到一个朴素的`DP`算法。
设 $f[u][0]$ 为不选 $u$ 的最佳答案, $f[u][1]$ 为选 $u$ 的最佳答案。
设 $v$ 为 $u$ 的直接儿子,则有转移:
$f[u][0]=\max_v\max(f[v][0],f[v][1])$
$f[u][1]=w[u]+\max_vf[v][0]$
然后按照套路把树剖了,对每条重链维护这些`DP`值。
设 $g$ 为其子树中轻儿子的`DP`值,也就是说只缺重儿子。
那么,一条重链的转移是 :
$f[i][0]=\max(f[i+1][0],f[i+1][1])+g[i][0]$
$f[i][1]=f[i+1][0]+g[i][1]$
做完一条重链之后,会产生对重链顶的父亲的 $g$ 的一次修改,这是平凡的。
然后这些东西可以大力讨论来转移,但是太麻烦了,而且可能产生不友好的细节。
这里有一个技巧 : 把`DP`的转移写成满足结合律的矩阵乘法的形式,可以大大简化讨论。
这里是最优化问题,我们选择 `Folyd` 矩阵,即 $\{max,+\}$ 矩阵,不难验证其具有结合律。
乘法是这样的 :
$$C[i][j]=\max_kA[i][k]+B[k][j]$$
假如我们对于 $g[i]$ 构造的矩阵为 $B$ ,而 $f[i]$ 的向量为 $C$ , $f[i+1]$ 的向量为 $A$。
我们需要满足 $C=B*A$
展开一下 : $\begin{cases}C[0]=\max(B[0][0]+A[0],B[0][1]+A[1])\\C[1]=\max(B[1][0]+A[0],B[1][1]+A[1])\end{cases}$
结合上面的转移式,不难推出转移矩阵为 $\begin{bmatrix}g[i][0]&g[i][0]\\g[i][1]&-\inf\end{bmatrix}$。
现在就是要支持修改一个矩阵,并维护所有矩阵的连乘积。
由于矩阵乘法具有结合律,使用线段树维护即可。复杂度 $O(n\log^2n)$。
某条重链顶的`DP`值,即为重链矩阵乘积的 $a,c$ 且 $f[u][0]=a,f[u][1]=c$ ,原因请读者自己思考。
```cpp
#include<algorithm>
#include<vector>
#include<cstdio>
#define MaxN 105000
using namespace std;
inline int read(){
int X=0;char ch=0,w=0;
while(ch<48||ch>57)ch=getchar(),w|=(ch=='-');
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return w?-X:X;
}
vector<int> g[MaxN];
struct Node
{int f,tf,son,siz,dep,ed;}b[MaxN];
void pfs1(int u)
{
b[u].siz=1;
for (int i=0,v;i<g[u].size();i++)
if (!b[v=g[u][i]].siz){
b[v].dep=b[b[v].f=u].dep+1;
pfs1(v);
b[u].siz+=b[v].siz;
if (b[v].siz>b[b[u].son].siz)
b[u].son=v;
}
}
int id[MaxN],tp[MaxN],tim,
f[MaxN][2],lf[MaxN][2],mp[MaxN];
void pfs2(int u,int tf)
{
b[tp[id[u]=++tim]=u].tf=tf;
if (!b[u].son){
f[u][1]=lf[u][1]=mp[u];
b[u].ed=u;
return ;
}pfs2(b[u].son,tf);
int v;
for (int i=0;i<g[u].size();i++){
if (!b[v=g[u][i]].tf){
pfs2(v,v);
lf[u][0]+=max(f[v][0],f[v][1]);
lf[u][1]+=f[v][0];
}
}lf[u][1]+=mp[u];
v=b[u].son;
f[u][0]=lf[u][0]+max(f[v][0],f[v][1]);
f[u][1]=lf[u][1]+f[v][0];
b[u].ed=b[v].ed;
}
#define INF 100000000
struct Matrix
{
int a[2][2];
Matrix operator * (const Matrix &B) const{
Matrix S;
S.a[0][0]=max(a[0][0]+B.a[0][0],a[0][1]+B.a[1][0]);
S.a[0][1]=max(a[0][0]+B.a[0][1],a[0][1]+B.a[1][1]);
S.a[1][0]=max(a[1][0]+B.a[0][0],a[1][1]+B.a[1][0]);
S.a[1][1]=max(a[1][0]+B.a[0][1],a[1][1]+B.a[1][1]);
return S;
}
}a[MaxN<<2];
inline void up(int u)
{a[u]=a[u<<1]*a[u<<1|1];}
int n;
void build(int l=1,int r=n,int u=1)
{
if (l==r){
a[u].a[0][0]=lf[tp[l]][0];
a[u].a[0][1]=lf[tp[l]][0];
a[u].a[1][0]=lf[tp[l]][1];
a[u].a[1][1]=-INF;
return ;
}int mid=(l+r)>>1;
build(l,mid,u<<1);
build(mid+1,r,u<<1|1);
up(u);
}
int to;
void chg(int l=1,int r=n,int u=1)
{
if (l==r){
a[u].a[0][0]=lf[tp[l]][0];
a[u].a[0][1]=lf[tp[l]][0];
a[u].a[1][0]=lf[tp[l]][1];
return ;
}int mid=(l+r)>>1;
if (to<=mid)chg(l,mid,u<<1);
else chg(mid+1,r,u<<1|1);
up(u);
}
int wfl,wfr;
Matrix qry(int l=1,int r=n,int u=1)
{
if (wfl<=l&&r<=wfr)return a[u];
int mid=(l+r)>>1;
if (wfl<=mid&&wfr>mid)
return qry(l,mid,u<<1)*qry(mid+1,r,u<<1|1);
return wfl<=mid ? qry(l,mid,u<<1) : qry(mid+1,r,u<<1|1);
}
void pathop(int u,int c)
{
lf[u][1]+=c-mp[u];mp[u]=c;
Matrix s;
while(1){
to=id[u];chg();
wfl=id[u=b[u].tf];wfr=id[b[u].ed];
s=qry();
if (u==1)break;
lf[b[u].f][1]+=s.a[0][0]-f[u][0];
lf[b[u].f][0]+=max(s.a[0][0],s.a[1][0])-max(f[u][0],f[u][1]);
f[u][0]=s.a[0][0];f[u][1]=s.a[1][0];
u=b[u].f;
}printf("%d\n",max(s.a[0][0],s.a[1][0]));
}
int m;
int main()
{
n=read();m=read();
for (int i=1;i<=n;i++)mp[i]=read();
for (int i=1,f,t;i<n;i++){
f=read();t=read();
g[f].push_back(t);
g[t].push_back(f);
}b[1].dep=1;pfs1(1);pfs2(1,1);
build();
for (int i=1,u,c;i<=m;i++){
u=read();c=read();
pathop(u,c);
}return 0;
}
```
- [P4751 【模板】"动态DP"&动态树分治(加强版)](https://www.luogu.com.cn/problem/P4751)
当然,也可以使用全局平衡二叉树来维护,这样复杂度是 $O(n\log n)$ 的。
```cpp
```
- [P6021 洪水](https://www.luogu.com.cn/problem/P6021)
设 $f[u]$ 为防卫 $u$ 的子树的最小代价。
朴素 `DP` 有 : $f[u]=\min(w[u],\sum\limits_{v\in son[u]}f[v])$
树剖一下,记 $lf[u]$ 为 $u$ 轻儿子的 $f$ 和。
方程变为 $f[u]=\min(w[u],f[u+1]+lf[u])$
把转移写成 `Folyd` 矩阵的形式可得 :
$\begin{bmatrix}f[u]\\0\end{bmatrix}=\begin{bmatrix}lf[u]&w[u]\\\inf&0\end{bmatrix}*\begin{bmatrix}f[u+1]\\0\end{bmatrix}$
注意到 $\begin{bmatrix}a_1&b_1\\\inf&0\end{bmatrix}*\begin{bmatrix}a_2&b_2\\\inf&0\end{bmatrix}=\begin{bmatrix}a_1+a_2&\min(b_1,a_1+b_2)\\\inf&0\end{bmatrix}$
这样我们就只需要维护两个变量了。
一个子树的`DP`值为根到所在重链底端的矩阵乘积。
注意需要`long long`。
```cpp
#include<algorithm>
#include<vector>
#include<cstdio>
#define ll long long
#define MaxN 205000
using namespace std;
inline char readc(){
char ch=0;
while(ch!='Q'&&ch!='C')ch=getchar();
return ch;
}
inline int read(){
int X=0;char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
vector<int> g[MaxN];
struct Node
{int f,tf,son,siz,dep,ed;}b[MaxN];
void pfs1(int u)
{
b[u].siz=1;
for (int i=0,v;i<g[u].size();i++)
if (!b[v=g[u][i]].siz){
b[v].dep=b[b[v].f=u].dep+1;
pfs1(v);
b[u].siz+=b[v].siz;
if (b[v].siz>b[b[u].son].siz)
b[u].son=v;
}
}
int id[MaxN],tp[MaxN],tim;
ll f[MaxN],lf[MaxN],mp[MaxN];
void pfs2(int u,int tf)
{
b[tp[id[u]=++tim]=u].tf=tf;
if (!b[u].son){
f[b[u].ed=u]=mp[u];
return ;
}pfs2(b[u].son,tf);
int v;
for (int i=0;i<g[u].size();i++){
if (!b[v=g[u][i]].tf){
pfs2(v,v);
lf[u]+=f[v];
}
}v=b[u].son;
f[u]=min(lf[u]+f[v],mp[u]);
b[u].ed=b[v].ed;
}
#define INF (1ll<<60)
struct Matrix
{
ll a,b;
Matrix operator * (const Matrix &B) const
{return (Matrix){a+B.a,min(b,a+B.b)};}
}a[MaxN<<2];
inline void up(int u)
{a[u]=a[u<<1]*a[u<<1|1];}
int n;
void build(int l=1,int r=n,int u=1)
{
if (l==r){
a[u].a=lf[tp[l]];
a[u].b=mp[tp[l]];
return ;
}int mid=(l+r)>>1;
build(l,mid,u<<1);
build(mid+1,r,u<<1|1);
up(u);
}
int to;
void chg(int l=1,int r=n,int u=1)
{
if (l==r){
a[u].a=lf[tp[l]];
a[u].b=mp[tp[l]];
return ;
}int mid=(l+r)>>1;
if (to<=mid)chg(l,mid,u<<1);
else chg(mid+1,r,u<<1|1);
up(u);
}
int wfl,wfr;
Matrix qry(int l=1,int r=n,int u=1)
{
if (wfl<=l&&r<=wfr)return a[u];
int mid=(l+r)>>1;
if (wfl<=mid&&wfr>mid)
return qry(l,mid,u<<1)*qry(mid+1,r,u<<1|1);
return wfl<=mid ? qry(l,mid,u<<1) : qry(mid+1,r,u<<1|1);
}
void pathop(int u,int c)
{
mp[u]+=c;
while(1){
to=id[u];chg();
u=b[u].tf;
if (u==1)break;
wfl=id[u];wfr=id[b[u].ed];
Matrix s=qry();
lf[b[u].f]-=f[u];
lf[b[u].f]+=(f[u]=s.b);
u=b[u].f;
}
}
int m;
int main()
{
n=read();
for (int i=1;i<=n;i++)mp[i]=read();
for (int i=1,f,t;i<n;i++){
f=read();t=read();
g[f].push_back(t);
g[t].push_back(f);
}b[1].dep=1;pfs1(1);pfs2(1,1);
build();m=read();
for (int i=1,u;i<=m;i++)
if (readc()=='Q'){
wfl=id[u=read()];wfr=id[b[u].ed];
Matrix s=qry();
printf("%lld\n",s.b);
}else {
u=read();
pathop(u,read());
}
return 0;
}
```
- [P5024 保卫王国](https://www.luogu.com.cn/problem/P5024)
首先有最小点覆盖=全集-最大独立集。
那么就可以直接套用前面模板题的做法,不选就是把权值置为 $\inf$ ,选就是 $-\inf$。
但是此题的询问之间互不影响,所以没必要动用带修链分治结构,只需要倍增就够了。
预处理从某个点向上跳 $2^i$ 的转移矩阵连乘积,然后我们就能把某个点被钦定的`DP`值快速向祖先转移。
若 $a$ 是 $b$ 的祖先,直接将 $b$ 倍增到 $a$ 的直接儿子,然后从原来的 $a$ 中挖掉旧贡献,加入新贡献,再次倍增到根。
若 $a,b$ 无祖先关系,则都倍增至 $lca(a,b)$ 的直接儿子,然后仍然是挖和补,再次倍增到根。
其实本质就是在虚树上`DP`,所以说扩展到 $k$ 个点一样能做 (
写起来似乎比直接剖还要长,细节也不少……唯一的好处是(表面上)考察的都是noip级算法。
```cpp
#include<algorithm>
#include<vector>
#include<cstdio>
#define ll long long
#define INF (1ll<<60)
#define MaxN 105000
using namespace std;
inline int read(){
int X=0;char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
vector<int> g[MaxN];
struct Matrix
{
ll a[2][2];
void Init(ll c1,ll c2){
a[0][0]=a[0][1]=c1;
a[1][0]=c2;a[1][1]=-INF;
}
void I(){
a[0][1]=a[1][0]=-INF;
a[0][0]=a[1][1]=0;
}
Matrix operator * (const Matrix &B) const{
Matrix S;
S.a[0][0]=max(a[0][0]+B.a[0][0],a[0][1]+B.a[1][0]);
S.a[0][1]=max(a[0][0]+B.a[0][1],a[0][1]+B.a[1][1]);
S.a[1][0]=max(a[1][0]+B.a[0][0],a[1][1]+B.a[1][0]);
S.a[1][1]=max(a[1][0]+B.a[0][1],a[1][1]+B.a[1][1]);
return S;
}
}c[18][MaxN];
ll sum,dp[MaxN][2],mp[MaxN];
int f[18][MaxN],dep[MaxN];
void dfs(int u)
{
dp[u][1]=mp[u];
for (int i=0,v;i<g[u].size();i++)
if (!dep[v=g[u][i]]){
dep[v]=dep[u]+1;
dfs(v);
dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
}
for (int i=0,v;i<g[u].size();i++)
if (dep[v=g[u][i]]>dep[u]){
f[0][v]=u;
c[0][v].Init(
dp[u][0]-max(dp[v][0],dp[v][1]),
dp[u][1]-dp[v][0]
);
}
}
int lca(int x,int y)
{
int k=17;
if (dep[x]>dep[y])swap(x,y);
while(k--)
while(dep[f[k][y]]>=dep[x])
y=f[k][y];
if (x==y)return x;
k=17;
while(k--)
while(f[k][x]!=f[k][y])
{x=f[k][x];y=f[k][y];}
return f[0][x];
}
int getc(int u,int v,Matrix &S)
{
int k=17;S.I();
while(k--)
while(dep[f[k][u]]>dep[v])
{S=c[k][u]*S;u=f[k][u];}
return u;
}
void solve()
{
int a=read(),x=read()^1,b=read(),y=read()^1,
t=lca(a,b);
ll s0,s1,st0,st1;Matrix S;
if (dep[a]>dep[b]){swap(a,b);swap(x,y);}
if (t==a){
int tc=getc(b,a,S);
st0=S.a[0][y]+dp[b][y];
st1=S.a[1][y]+dp[b][y];
if (x){
s1=dp[a][1]-dp[tc][0]+st0;
s0=-INF;
}else {
s0=dp[a][0]-max(dp[tc][0],dp[tc][1])
+max(st0,st1);
s1=-INF;
}
}else {
int ta=getc(a,t,S);
st0=S.a[0][x]+dp[a][x];
st1=S.a[1][x]+dp[a][x];
s0=dp[t][0]-max(dp[ta][0],dp[ta][1])+max(st0,st1);
s1=dp[t][1]-dp[ta][0]+st0;
int tb=getc(b,t,S);
st0=S.a[0][y]+dp[b][y];
st1=S.a[1][y]+dp[b][y];
s0=s0-max(dp[tb][0],dp[tb][1])+max(st0,st1);
s1=s1-dp[tb][0]+st0;
}getc(t,0,S);
ll ans=max(
max(S.a[0][0],S.a[1][0])+s0,
max(S.a[0][1],S.a[1][1])+s1
);printf("%lld\n", ans<0 ? -1 : sum-ans);
}
int n,m;char typ[10];
int main()
{
n=read();m=read();scanf("%s",typ);
for (int i=1;i<=n;i++)
sum+=(mp[i]=read());
for (int i=1,f,t;i<n;i++){
f=read();t=read();
g[f].push_back(t);
g[t].push_back(f);
}dep[1]=1;dfs(1);
for (int j=1;j<17;j++)
for (int i=1;i<=n;i++){
f[j][i]=f[j-1][f[j-1][i]];
c[j][i]=c[j-1][f[j-1][i]]*c[j-1][i];
}
for (int i=1;i<=m;i++)solve();
return 0;
}
```
- [P3781 [SDOI2017]切树游戏](https://www.luogu.com.cn/problem/P3781)
**题意** : 给出一棵 $n$ 个点的树,有点权,均在 $[0,m)$ 内。
要求资瓷如下两种操作:
- 修改某个点的点权。
- 查询有多少个非空连通块,其点权异或和恰为 $k$。答案对 $10007$ 取模。
$n,q\leq 30000,m\leq 128$ ,修改不超过 $10^4$ 个,时限$\texttt{3s}$。
------------
先不考虑修改,可以把每个点当成一个集合幂级数 : $1+x^w$
然后对“每个非空连通块幂级数的异或卷积”求和,即可得到答案。
首先跑一个`DWT`变成点值,然后对每一位点值,就变成了如下问题 :
对“每个非空连通块幂级数的点值乘积”求和。
设 $f[u]$ 为 $u$ 子树内必选 $u$ 的答案,这样,每个连通块会在其最浅点被统计到。
假设子树 $v$ 转移到 $u$ ,可以选择不乘,也可以选择乘,能得到这样的转移 :
$f[u]\leftarrow f[u]f[v]+f[v]$
最后 $\sum\limits_{i=1}^nf[i]$ 即为答案。
暴力`DP`一次是 $O(nm)$ 的,无法通过。
考虑动态 `DP` ,把树剖了。
对于一条重链,设 $lf[i]$ 为该点的轻儿子`DP`值(+1)乘积。
注意到不可能每次求和来统计答案,所以我们还要额外记 $s[i]$ 表示子树内 $f[i]$ 的和, $ls[i]$ 为轻儿子的 $f$ 和。
那么可以写出转移 :
$f[i]=(f[i+1]+1)lf[i]=f[i+1]lf[i]+lf[i]$
$s[i]=s[i+1]+f[i]+ls[i]=s[i+1]+ls[i]+f[i+1]lf[i]+lf[i]$
然后我们来手玩一下怎么用 $lf[i],ls[i]$ 拼成一个合适的转移矩阵,假设为 $B$。
若 $f[i],s[i]$ 的向量为 $C$ , $f[i+1],s[i+1]$ 的向量为 $B$。
我们需要满足 $C=B*A$.
注意到转移式中有许多系数为 $1$ 的转移, $2\times 2$ 的矩阵可能无法满足需求,我们使用 $3\times 3$ 的。
使用待定系数法或者直接考虑递推式的含义不难写出:
$\begin{bmatrix}f[i]\\s[i]\\1\end{bmatrix}=\begin{bmatrix}lf[i]&0&lf[i]\\lf[i]&1&ls[i]+lf[i]\\0&0&1\end{bmatrix}*\begin{bmatrix}f[i+1]\\s[i+1]\\1\end{bmatrix}$
然后按照套路使用线段树维护即可。
这样的常数是 $3^3=27$ ,较大。事实上,由于矩阵比较特殊,可以进一步减小常数 :
$\begin{bmatrix}a_1&0&b_1\\c_1&1&d_1\\0&0&1\end{bmatrix}*\begin{bmatrix}a_2&0&b_2\\c_2&1&d_2\\0&0&1\end{bmatrix}=\begin{bmatrix}a_1a_2&0&b_1+a_1b_2\\c_1a_2+c_2&1&c_1b_2+d_1+d_2\\0&0&1\end{bmatrix}$
也就是说我们实际上只需要维护 $4$ 个数。
实际转移是 :
$\begin{cases}a_3=a_1a_2\\b_3=b_1+a_1b_2\\c_3=c_1a_2+c_2\\d_3=c_1b_2+d_1+d_2\end{cases}$
这样就把常数大大减小了。
但是注意,每条重链处理完毕时,需要对父亲的轻边信息进行单点修改。
此时会对 $lf$ 产生一个除法操作,然而在膜意义下可能没有逆元。
一种方法是给轻儿子也开线段树维护树的连乘积,另一种办法是记录膜意义下的 $0$ 有多少个。
一开始听说记录 $0$ 更好写,可是个人感觉细节有点多,而且不便于封装(这是最大的麻烦),所以还是选择写多一棵线段树来维护轻儿子,顺便以备将来贡献无法撤销时使用。
我们对于并行的 $m$ 组点值都采用上述操作,最终做一次暴力 $IDWT$ 就能得到答案。
最终复杂度是 $O(nm+qm(\log^2n+\log m))$。
Loj可过,但是由于某些毒瘤在洛谷上加了卡树剖的数据,并不能通过。
```cpp
#include<algorithm>
#include<vector>
#include<cstdio>
#define MaxN 30500
#define MaxM 130
using namespace std;
const int mod=10007;
int m,invm;
void FWT(int *f)
{
for (int l=1;l<m;l<<=1)
for (int p=0;p<m;p+=l+l)
for (int k=p;k<p+l;k++){
int s0=f[k];
f[k]+=f[k+l];
f[k+l]=s0-f[k+l];
}
for (int i=0;i<m;i++)f[i]=(f[i]+mod*mod)%mod;
}
void IFWT(int *f)
{FWT(f);for (int i=0;i<m;i++)f[i]=f[i]*invm%mod;}
inline int pm0(int x)
{return x<0 ? x+mod : x;}
inline int pm1(int x)
{return x>=mod ? x-mod : x;}
struct Data{
int a[MaxM];
void clear()
{for (int i=0;i<m;i++)a[i]=0;}
inline Data operator + (const Data &B) const
{Data C;for (int i=0;i<m;i++)C.a[i]=pm1(a[i]+B.a[i]);return C;}
inline Data operator - (const Data &B) const
{Data C;for (int i=0;i<m;i++)C.a[i]=pm0(a[i]-B.a[i]);return C;}
inline Data operator * (const Data &B) const
{Data C;for (int i=0;i<m;i++)C.a[i]=a[i]*B.a[i]%mod;return C;}
}one;
struct Matrix{
Data a,b,c,d;
inline Matrix operator * (const Matrix &B) const
{return (Matrix){a*B.a,b+a*B.b,c*B.a+B.c,c*B.b+d+B.d};}
};
int to,wfl,wfr;
template <class Type>
class SGT {
public:
Type a[MaxN<<2],wfc,s[MaxN];
int lim,id[MaxN],tim;
inline void up(int u){a[u]=a[u<<1]*a[u<<1|1];}
void build(int l,int r,int u=1)
{
if (l==r){a[u]=s[l];return ;}
int mid=(l+r)>>1;
build(l,mid,u<<1);
build(mid+1,r,u<<1|1);
up(u);
}
void chg(int l,int r,int u=1)
{
if (l==r){a[u]=wfc;return ;}
int mid=(l+r)>>1;
if (to<=mid)chg(l,mid,u<<1);
else chg(mid+1,r,u<<1|1);
up(u);
}
Type qry(int l,int r,int u=1)
{
if (wfl<=l&&r<=wfr)return a[u];
int mid=(l+r)>>1;
if (wfl<=mid&&wfr>mid)
return qry(l,mid,u<<1)*qry(mid+1,r,u<<1|1);
return wfl<=mid ? qry(l,mid,u<<1) : qry(mid+1,r,u<<1|1);
}
};
SGT<Matrix> T;
SGT<Data> L;
vector<int> g[MaxN];
struct Node
{int f,tf,son,siz,dep,ed,tl,tr;}b[MaxN];
void pfs1(int u)
{
b[u].siz=1;
for (int i=0,v;i<g[u].size();i++)
if (!b[v=g[u][i]].siz){
b[v].dep=b[b[v].f=u].dep+1;
pfs1(v);
b[u].siz+=b[v].siz;
if (b[v].siz>b[b[u].son].siz)
b[u].son=v;
}
}
Data f[MaxN],lf[MaxN],s[MaxN],ls[MaxN],sp[MaxN];
int mp[MaxN];
void pfs2(int u,int tf)
{
b[u].tf=tf;
T.id[u]=++T.tim;
sp[u].a[mp[u]]=1;FWT(sp[u].a);lf[u]=sp[u];
if (!b[u].son){f[u]=s[u]=sp[u];b[u].ed=u;return ;}
pfs2(b[u].son,tf);
int v;
for (int i=0;i<g[u].size();i++)
if (!b[v=g[u][i]].tf)
pfs2(v,v);
b[u].tl=L.tim+1;
for (int i=0;i<g[u].size();i++){
if ((v=g[u][i])!=b[u].f&&v!=b[u].son){
L.id[v]=++L.tim;
ls[u]=ls[u]+s[v];
lf[u]=lf[u]*f[v]+lf[u];
}
}b[u].tr=L.tim;
v=b[u].son;
f[u]=lf[u]*f[v]+lf[u];
s[u]=ls[u]+s[v]+f[u];
b[u].ed=b[v].ed;
}
Data ans;
void pathop(int u,int c)
{
sp[u].clear();
sp[u].a[mp[u]=c]=1;FWT(sp[u].a);
wfl=b[u].tl;wfr=b[u].tr;
if (0<wfl&&wfl<=wfr)lf[u]=sp[u]*L.qry(1,L.tim);
else lf[u]=sp[u];
Matrix S;
while(1){
to=T.id[u];
T.wfc=(Matrix){lf[u],lf[u],lf[u],ls[u]+lf[u]};
T.chg(1,T.tim);
wfl=T.id[u=b[u].tf];wfr=T.id[b[u].ed];
S=T.qry(1,T.tim);
if (u==1)break;
ls[b[u].f]=ls[b[u].f]-s[u]+S.d;s[u]=S.d;
to=L.id[u];L.wfc=S.b+one;
L.chg(1,L.tim);
wfl=b[b[u].f].tl;wfr=b[b[u].f].tr;
lf[b[u].f]=sp[b[u].f]*L.qry(1,L.tim);
u=b[u].f;
}ans=S.d;IFWT(ans.a);
}
void Init()
{
for (int i=1;i<mod;i++)
if (m*i%mod==1){invm=i;break;}
for (int i=0;i<m;i++)one.a[i]=1;
}
int n,q;char op[10];
int main()
{
scanf("%d%d",&n,&m);Init();
for (int i=1;i<=n;i++)scanf("%d",&mp[i]);
for (int i=1,f,t;i<n;i++){
scanf("%d%d",&f,&t);
g[f].push_back(t);
g[t].push_back(f);
}b[1].dep=1;pfs1(1);pfs2(1,1);
for (int i=1;i<=n;i++)
T.s[T.id[i]]=(Matrix){lf[i],lf[i],lf[i],ls[i]+lf[i]};
T.build(1,T.tim);
for (int i=1;i<=n;i++)
if (L.id[i])
L.s[L.id[i]]=f[i]+one;
if (L.tim)L.build(1,L.tim);
ans=s[1];IFWT(ans.a);
scanf("%d",&q);
for (int i=1,u,c;i<=q;i++){
scanf("%s",op);
if (op[0]=='Q'){
scanf("%d",&c);
printf("%d\n",ans.a[c]);
}else {
scanf("%d%d",&u,&c);
pathop(u,c);
}
}return 0;
}
```