ZR 集训 Day11 - 矩阵、递推与动态 DP
P1349 广义斐波那契数列
$$
\begin{bmatrix}
a_i & a_{i+1}
\end{bmatrix}
\times
\begin{bmatrix}
0 & q \\
1 & p
\end{bmatrix}
=
\begin{bmatrix}
a_{i+1} & q\times a_i+p\times a_{i+1}
\end{bmatrix}
=
\begin{bmatrix}
a_{i+1} & a_{i+2}
\end{bmatrix}
$$
使用矩阵快速幂加速即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,mod,p,q,a1,a2;
struct matrix{
int c[3][3];
matrix(){memset(c,0,sizeof c);}
};
matrix operator*(const matrix &x,const matrix &y){
matrix tmp;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
for(int k=1;k<=2;k++)
(tmp.c[i][j]+=x.c[i][k]*y.c[k][j]%mod)%=mod;
return tmp;
}
matrix qpow(matrix A,int k){
matrix res;
for(int i=1;i<=2;i++) res.c[i][i]=1;
while(k){
if(k&1) res=res*A;
A=A*A,k>>=1;
}
return res;
}
signed main(){
matrix A,B;
cin>>p>>q>>a1>>a2>>n>>mod;
if(n==1) return cout<<a1%mod,0;
if(n==2) return cout<<a2%mod,0;
A.c[1][1]=0,A.c[1][2]=q,A.c[2][1]=1,A.c[2][2]=p;
B.c[1][1]=a1,B.c[1][2]=a2;
A=qpow(A,n-1);
B=B*A;
cout<<B.c[1][1];
return 0;
}CF1152F2 Neko Rules the Catniverse (Large Version)
也是做上黑题了。
这题跳来跳去是无序的,所以我们考虑枚举每一个星球,看是否能加入当前跳跃序列中。
我们用一个 $m$ 位二进制数 $S$ 维护一个滑动窗口,表示区间 $[i-m,i-1]$ 内选了哪些星球。
可以知道如果选择当前星球,可行的方案数应该是 $popcount(S)+1$,即可以加在整个跳跃序列的开头,或者每个星球的后面。
设 $dp_{i,j,S}$ 表示选到了第 $i$ 个星球,共 $j$ 次跳跃,滑动窗口状态为 $S$ 时的答案,转移方程分选 $i+1$ 和不选 $i+1$ 进行讨论,根据上面的方案书列出来即可,这里略。
会发现转移时和 $i$ 无关,并且转移只涉及乘法,而 $j$ 和 $S$ 的状态数又很小,所以考虑把 $j,S$ 压成一维,进行矩阵快速幂加速。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,k,m,sz;
struct matrix{
int c[210][210];
matrix(){memset(c,0,sizeof c);}
};
matrix operator*(const matrix &x,const matrix &y){
matrix tmp;
for(int i=1;i<=sz;i++)
for(int k=1;k<=sz;k++)
if(x.c[i][k])
for(int j=1;j<=sz;j++)
(tmp.c[i][j]+=x.c[i][k]*y.c[k][j]%mod)%=mod;
return tmp;
}
matrix qpow(matrix A,int k){
matrix res;
for(int i=1;i<=sz;i++) res.c[i][i]=1;
while(k){
if(k&1) res=res*A;
A=A*A,k>>=1;
}
return res;
}
int get_id(int j,int S){
return j*(1<<m)+S+1;
}
signed main(){
cin>>n>>k>>m;
sz=(k+1)*(1<<m);
matrix A,B;
int mask=(1<<m)-1;
for(int j=0;j<=k;j++){
for(int S=0;S<(1<<m);S++){
int u=get_id(j,S);
int S0=(S<<1)&mask;
(A.c[u][get_id(j,S0)]+=1)%=mod;
if(j<k){
int S1=S0|1;
int w=__builtin_popcount(S)+1;
(A.c[u][get_id(j+1,S1)]+=w)%=mod;
}
}
}
B.c[1][get_id(0,0)]=1;
A=qpow(A,n);
B=B*A;
int ans=0;
for(int S=0;S<(1<<m);S++) (ans+=B.c[1][get_id(k,S)])%=mod;
cout<<ans;
return 0;
}CF1117D Magic Gems
很容易推出转移方程:
$$
dp_i=dp_{i-1}+dp_{i-m}
$$
由于 $n$ 很大,考虑矩阵快速幂优化。
构造以下矩阵:
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,k,m;
struct matrix{
int c[110][110];
matrix(){memset(c,0,sizeof c);}
};
matrix operator*(const matrix &x,const matrix &y){
matrix tmp;
for(int i=1;i<=m;i++)
for(int k=1;k<=m;k++)
if(x.c[i][k])
for(int j=1;j<=m;j++)
(tmp.c[i][j]+=x.c[i][k]*y.c[k][j]%mod)%=mod;
return tmp;
}
matrix qpow(matrix A,int k){
matrix res;
for(int i=1;i<=m;i++) res.c[i][i]=1;
while(k){
if(k&1) res=res*A;
A=A*A,k>>=1;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
matrix A,B;
cin>>n>>m;
if(n-m+1<=0) return cout<<1,0;
for(int i=1;i<=m;i++) B.c[1][i]=1,A.c[i+1][i]=1;
A.c[1][m]=1,A.c[m][m]=1;
A=qpow(A,n-m+1);
B=B*A;
cout<<B.c[1][m];
return 0;
}P1912 [NOI2009] 诗人小G
列出状态转移方程:
$$
\begin{aligned}
dp_i&=\min\{dp_j+w(j,i)\} \\
w(j,i)&=|sum_i-sum_j+i-j-1-L|^P
\end{aligned}
$$
不难发现 $w(i,j)$ 是满足四边形不等式的,所以这个 DP 满足决策单调性。
对于有决策单调性的 DP,我们通常使用单调队列+二分来进行优化。
我们用单调队列维护当前最优决策点,用一个数组 $a$ 表示第 $i$ 个点作为决策点时,将会在第 $a_{i-1}$ 个点时优于第 $i-1$ 个点。向队列添加新点时,我们将其与队尾进行比较,若新的 $a_{tail}$ 要小于 $a_{tail-1}$,就弹出,最后把当前决策点加入。
详细实现看代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long double ld;
const int N=1e5+5;
int T,n,L,P;
int sum[N],opt[N],q[N],a[N],pm[N];
ld dp[N];
string str[N];
ld qpow(ld b,int p){
ld a=1;
while(p){
if(p&1) a*=b;
b*=b;
p>>=1;
}
return a;
}
ld calc(int i,int j){
return dp[j]+qpow(fabsl((ld)(sum[i]-sum[j]+i-j-1-L)),P);
}
int bound(int x,int y){
int l=y,r=n+1;
while(l<r){
int mid=(l+r)>>1;
if(calc(mid,x)>=calc(mid,y)) r=mid;
else l=mid+1;
}
return l;
}
void solve(){
cin>>n>>L>>P;
for(int i=1;i<=n;i++){
cin>>str[i];
sum[i]=sum[i-1]+str[i].size();
}
int l=1,r=1;
for(int i=1;i<=n;i++){
while(l<r&&a[l]<=i) l++;
dp[i]=calc(i,q[l]),opt[i]=q[l];
while(l<r&&a[r-1]>=bound(q[r],i)) r--;
a[r]=bound(q[r],i),q[++r]=i;
}
if(dp[n]>1e18) cout<<"Too hard to arrange\n";
else{
cout<<fixed<<setprecision(0)<<dp[n]<<"\n";
int idx=0;
for(int i=n;i;i=opt[i]) pm[++idx]=i;
pm[++idx]=0;
for(int i=idx;i>1;i--){
int st=pm[i]+1,ed=pm[i-1];
for(int j=st;j<ed;j++) cout<<str[j]<<" ";
cout<<str[ed]<<"\n";
}
}
cout<<"--------------------\n";
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--) solve();
return 0;
}P3502 [POI 2010] CHO-Hamsters
用 $dis_{i,j}$ 表示第 $j$ 个字符串要接到第 $i$ 个字符串后面还需要几个字符,这里可以用字符串哈希先预处理一下。
设 $dp_{i,j}$ 表示当前串内共 $i$ 个名字,最后一个名字为 $j$,就得到了转移方程:
$$
dp_{i,j}=\min\{dp_{i-1,k}+dis_{k,j}\}(k\in [1,n])
$$
发现如果把 $\min$ 换成求和,把加法换成乘法,这就变成了矩阵乘法。
所以我们使用广义矩阵乘法进行矩阵快速幂优化,即用取最小值代替加法、用加法代替乘法。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef unsigned long long ull;
const int N=505,M=10005,base=101,inf=1e18;
int n,m,len[N];
string s[N];
ull mi[M],ha[M],hb[M];
struct mat{
int a[N][N];
mat(){for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) a[i][j]=inf;}
};
mat operator*(mat x,mat y){
mat z;
for(int i=0;i<=n;i++) for(int k=0;k<=n;k++) if(x.a[i][k]!=inf)
for(int j=0;j<=n;j++) if(x.a[i][k]+y.a[k][j]<z.a[i][j]) z.a[i][j]=x.a[i][k]+y.a[k][j];
return z;
}
mat qpow(mat x,int k){
mat res;
for(int i=0;i<=n;i++) res.a[i][i]=0;
while(k){
if(k&1) res=res*x;
x=x*x,k>>=1;
}
return res;
}
int calc(int x,int y,int same){
for(int i=1;i<=len[x];i++) ha[i]=ha[i-1]*base+s[x][i-1];
for(int i=1;i<=len[y];i++) hb[i]=hb[i-1]*base+s[y][i-1];
for(int i=len[y]-same;i>=1;i--){
if(len[x]<i) continue;
ull h1=hb[i],h2=ha[len[x]]-ha[len[x]-i]*mi[i];
if(h1==h2) return len[y]-i;
}
return len[y];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i],len[i]=s[i].size();
mi[0]=1;
for(int i=1;i<M;i++) mi[i]=mi[i-1]*base;
mat A;
for(int i=0;i<=n;i++) for(int j=0;j<=n;j++){
if(j==0) A.a[i][j]=inf;
else if(i==0) A.a[i][j]=len[j];
else A.a[i][j]=calc(i,j,i==j);
}
A=qpow(A,m);
int ans=inf;
for(int i=1;i<=n;i++) if(A.a[0][i]<ans) ans=A.a[0][i];
cout<<ans<<"\n";
return 0;
}P4719 【模板】动态 DP
对于动态树上最大权独立集问题,我们首先考虑静态树 DP 的方程。设 $f_{u,0}$ 表示不选 $u$ 时 $u$ 子树的最大权独立集,$f_{u,1}$ 表示选择 $u$ 时 $u$ 子树的最大权独立集,易得转移方程为 $f_{u,0} = \sum_{v \in son(u)} \max(f_{v,0}, f_{v,1})$ 以及 $f_{u,1} = a_u + \sum_{v \in son(u)} f_{v,0}$。
为了在动态修改点权时高效维护 DP 值,我们需要借助树链剖分将子树贡献拆分为重儿子 $hs(u)$ 和轻儿子集合 $Light(u)$。我们定义辅助数组 $g_{u,0}$ 和 $g_{u,1}$ 分别存储轻儿子对 $u$ 的贡献:$g_{u,0} = \sum_{v \in Light(u)} \max(f_{v,0}, f_{v,1})$,$g_{u,1} = a_u + \sum_{v \in Light(u)} f_{v,0}$。此时 DP 转移方程可以重写为 $f_{u,0} = g_{u,0} + \max(f_{hs(u),0}, f_{hs(u),1})$ 和 $f_{u,1} = g_{u,1} + f_{hs(u),0}$。
观察上述方程,若我们重新定义广义矩阵乘法 $(A \otimes B)_{i,j} = \max_k (A_{i,k} + B_{k,j})$(即加法映射为 $\max$,乘法映射为 $+$),则转移方程可以完美写成矩阵形式:
$$
\begin{bmatrix} f_{u,0} \\ f_{u,1} \end{bmatrix} = \begin{bmatrix} g_{u,0} & g_{u,0} \\ g_{u,1} & -\infty \end{bmatrix} \otimes \begin{bmatrix} f_{hs(u),0} \\ f_{hs(u),1} \end{bmatrix}
$$
令 $M_u = \begin{bmatrix} g_{u,0} & g_{u,0} \ g_{u,1} & -\infty \end{bmatrix}$,由于树链剖分保证同一条重链上的节点在 DFS 序中连续,因此一条重链上的 DP 值对应着链上各节点转移矩阵区间乘积的连续复合。我们使用线段树维护重链上节点的矩阵乘积,线段树每个节点维护区间矩阵乘积 $tr[p] = tr[lc] \otimes tr[rc]$。
当单点 $x$ 的权值发生修改时,我们先更新 $g_{x,1}$ 和 $x$ 对应的矩阵 $M_x$,并在线段树中更新点 $x$。随后我们沿着重链向上跳($x \leftarrow fa[top[x]]$),每次跳之前查询修改前该重链链顶的 DP 值,修改线段树后再查询新的链顶 DP 值,用两者的差值增量更新父节点 $fa[top[x]]$ 的 $g$ 数组,如此循环往复直至根节点。由于点到根节点最多经过 $O(\log n)$ 条重链,每次更新重链矩阵需 $O(\log n)$ 的线段树查询与修改,因此单次修改的时间复杂度为 $O(\log^2 n)$,整体时间复杂度为 $O((n+m) \log^2 n)$,空间复杂度为 $O(n)$。
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
const int N=100005,inf=1e9;
int n,m;
int a[N];
int dp[N],fa[N],sz[N],sn[N];
int id[N],wt[N],tp[N],ed[N],idx;
int f[N][2],g[N][2];
vector<int> t[N];
struct mat{
int c[2][2];
mat(){
c[0][0]=c[0][1]=c[1][0]=c[1][1]=-inf;
}
}vl[N],tr[N<<2];
mat operator*(const mat &x,const mat &y){
mat tmp;
for(int i=0;i<2;i++){
for(int k=0;k<2;k++){
for(int j=0;j<2;j++){
tmp.c[i][j]=max(tmp.c[i][j],x.c[i][k]+y.c[k][j]);
}
}
}
return tmp;
}
void df1(int u,int p,int d){
dp[u]=d,fa[u]=p,sz[u]=1;
int mx=-1;
for(int i=0;i<t[u].size();i++){
int v=t[u][i];
if(v==p) continue;
df1(v,u,d+1);
sz[u]+=sz[v];
if(sz[v]>mx) mx=sz[v],sn[u]=v;
}
}
void df2(int u,int tf){
id[u]=++idx,wt[idx]=u,tp[u]=tf,ed[tf]=u;
f[u][0]=0,f[u][1]=a[u];
g[u][0]=0,g[u][1]=a[u];
if(sn[u]){
df2(sn[u],tf);
f[u][0]+=max(f[sn[u]][0],f[sn[u]][1]);
f[u][1]+=f[sn[u]][0];
}
for(int i=0;i<t[u].size();i++){
int v=t[u][i];
if(v==fa[u]||v==sn[u]) continue;
df2(v,v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
g[u][0]+=max(f[v][0],f[v][1]);
g[u][1]+=f[v][0];
}
vl[id[u]].c[0][0]=g[u][0],vl[id[u]].c[0][1]=g[u][0];
vl[id[u]].c[1][0]=g[u][1],vl[id[u]].c[1][1]=-inf;
}
void pup(int p){
tr[p]=tr[lc]*tr[rc];
}
void bld(int p,int l,int r){
if(l==r){
tr[p]=vl[l];
return;
}
int md=l+r>>1;
bld(lc,l,md);
bld(rc,md+1,r);
pup(p);
}
void chg(int p,int l,int r,int ps,const mat &k){
if(l==r){
tr[p]=k;
return;
}
int md=l+r>>1;
if(ps<=md) chg(lc,l,md,ps,k);
else chg(rc,md+1,r,ps,k);
pup(p);
}
mat que(int p,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return tr[p];
int md=l+r>>1;
if(qr<=md) return que(lc,l,md,ql,qr);
if(ql>md) return que(rc,md+1,r,ql,qr);
return que(lc,l,md,ql,qr)*que(rc,md+1,r,ql,qr);
}
void upd(int x,int y){
g[x][1]+=y-a[x];
a[x]=y;
while(x){
mat om=que(1,1,n,id[tp[x]],id[ed[tp[x]]]);
int o0=max(om.c[0][0],om.c[0][1]);
int o1=max(om.c[1][0],om.c[1][1]);
vl[id[x]].c[0][0]=g[x][0],vl[id[x]].c[0][1]=g[x][0];
vl[id[x]].c[1][0]=g[x][1],vl[id[x]].c[1][1]=-inf;
chg(1,1,n,id[x],vl[id[x]]);
mat nm=que(1,1,n,id[tp[x]],id[ed[tp[x]]]);
int n0=max(nm.c[0][0],nm.c[0][1]);
int n1=max(nm.c[1][0],nm.c[1][1]);
x=fa[tp[x]];
if(!x) break;
g[x][0]+=max(n0,n1)-max(o0,o1);
g[x][1]+=n0-o0;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
t[u].push_back(v);
t[v].push_back(u);
}
df1(1,0,1);
df2(1,1);
bld(1,1,n);
while(m--){
int x,y;
cin>>x>>y;
upd(x,y);
mat res=que(1,1,n,id[1],id[ed[1]]);
cout<<max(max(res.c[0][0],res.c[0][1]),max(res.c[1][0],res.c[1][1]))<<"\n";
}
return 0;
}


Comments NOTHING