动态DP小记
command_block · · 个人记录
最近你可能在本Blog看到许多这样的小记,这完全属于省选之前的游击战术,内容不保证严谨,也不保证大多数人能看懂。
如果省选过后有时间我会来仔细更新并且把这些文字去掉的。
- P4719 【模板】"动态 DP"&动态树分治
首先要得到一个朴素的DP算法。
设
设
然后按照套路把树剖了,对每条重链维护这些DP值。
设 DP值,也就是说只缺重儿子。
那么,一条重链的转移是 :
做完一条重链之后,会产生对重链顶的父亲的
然后这些东西可以大力讨论来转移,但是太麻烦了,而且可能产生不友好的细节。
这里有一个技巧 : 把DP的转移写成满足结合律的矩阵乘法的形式,可以大大简化讨论。
这里是最优化问题,我们选择 Folyd 矩阵,即
乘法是这样的 :
假如我们对于
我们需要满足
展开一下 :
结合上面的转移式,不难推出转移矩阵为
现在就是要支持修改一个矩阵,并维护所有矩阵的连乘积。
由于矩阵乘法具有结合律,使用线段树维护即可。复杂度
某条重链顶的DP值,即为重链矩阵乘积的
#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"&动态树分治(加强版)
当然,也可以使用全局平衡二叉树来维护,这样复杂度是
- P6021 洪水
设
朴素 DP 有 :
树剖一下,记
方程变为
把转移写成 Folyd 矩阵的形式可得 :
注意到
这样我们就只需要维护两个变量了。
一个子树的DP值为根到所在重链底端的矩阵乘积。
注意需要long long。
#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 保卫王国
首先有最小点覆盖=全集-最大独立集。
那么就可以直接套用前面模板题的做法,不选就是把权值置为
但是此题的询问之间互不影响,所以没必要动用带修链分治结构,只需要倍增就够了。
预处理从某个点向上跳 DP值快速向祖先转移。
若
若
其实本质就是在虚树上DP,所以说扩展到
写起来似乎比直接剖还要长,细节也不少……唯一的好处是(表面上)考察的都是noip级算法。
#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]切树游戏
题意 : 给出一棵
要求资瓷如下两种操作:
-
修改某个点的点权。
-
查询有多少个非空连通块,其点权异或和恰为
k 。答案对10007 取模。
然后对“每个非空连通块幂级数的异或卷积”求和,即可得到答案。
首先跑一个DWT变成点值,然后对每一位点值,就变成了如下问题 :
对“每个非空连通块幂级数的点值乘积”求和。
设
假设子树
最后
暴力DP一次是
考虑动态 DP ,把树剖了。
对于一条重链,设 DP值(+1)乘积。
注意到不可能每次求和来统计答案,所以我们还要额外记
那么可以写出转移 :
然后我们来手玩一下怎么用
若
我们需要满足
注意到转移式中有许多系数为
使用待定系数法或者直接考虑递推式的含义不难写出:
然后按照套路使用线段树维护即可。
这样的常数是
也就是说我们实际上只需要维护
实际转移是 :
这样就把常数大大减小了。
但是注意,每条重链处理完毕时,需要对父亲的轻边信息进行单点修改。
此时会对
一种方法是给轻儿子也开线段树维护树的连乘积,另一种办法是记录膜意义下的
一开始听说记录
我们对于并行的
最终复杂度是
Loj可过,但是由于某些毒瘤在洛谷上加了卡树剖的数据,并不能通过。
#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;
}