其实本来是不想写这篇博文的,但是5124这题没见过想写,单独写又有点短,于是乎多写一个凑数。
还有下面的原题地址是没有题面的,题面在这里。
BZOJ5124波浪序列
【题目】
原题地址
给定两个
X
X
维向量序列,求有多少个序列
f,g
f
,
g
满足
1≤f1<f2<...<fk≤n,1≤g1<g2<...<gk≤m
1
≤
f
1
<
f
2
<
.
.
.
<
f
k
≤
n
,
1
≤
g
1
<
g
2
<
.
.
.
<
g
k
≤
m
且
afi=bfi,[af1,af2,...,afk]
a
f
i
=
b
f
i
,
[
a
f
1
,
a
f
2
,
.
.
.
,
a
f
k
]
是波浪的(波浪指对于每个非两端
i
i
满足或
ai−1>ai<ai+1
a
i
−
1
>
a
i
<
a
i
+
1
)
【题目分析】
显然是dp,但是没有见过,不会优化。
【解题思路】
首先有一个很显然的dp方法,我们令
fi,j,k
f
i
,
j
,
k
表示只考虑
a[1..i]和b[1..j]
a
[
1..
i
]
和
b
[
1..
j
]
,
选择的两个子序列结尾分别是
ai和bj
a
i
和
b
j
,上升下降状态为
k
k
的方案数。
那么我们有,其中
x<i,y<j
x
<
i
,
y
<
j
。暴力转移的复杂度是
O(kn2m2)
O
(
k
n
2
m
2
)
的,显然不能接受。
我们可以考虑将决策点转移的方案数先dp掉,转移后面我们就可以用
O(1)
O
(
1
)
进行转移。
那么令
gi,y,k
g
i
,
y
,
k
表示从
fx,y,k
f
x
,
y
,
k
作为决策点出发,当前要更新的是
i
i
的方案数,
表示从
fx,y,k
f
x
,
y
,
k
作为决策点出发,已经经历了
g
g
的枚举,当前更新的是的方案数。
转移的话则是要么更新,要么将
i或j
i
或
j
枚举到
i+1以及j+1
i
+
1
以
及
j
+
1
。
因为每次只有一个变量在动,所以另一个变量可以表示上一个位置的值,可以表示上一个位置的值,方便判断上升还是下降。
这样做的时间复杂度就可以优化到
O(knm)
O
(
k
n
m
)
【参考代码】
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
const int M=7;
const int mod=998244353;
int n,m,ks,ans;
int a[N][M],b[N][M],f[N][N][2],g[N][N][2];
bool equal(int *x,int *y)
{
for(int i=1;i<=ks;++i)
if(x[i]^y[i])
return 0;
return 1;
}
bool bigger(int *x,int *y)
{
for(int i=1;i<=ks;++i)
if(x[i]<=y[i])
return 0;
return 1;
}
bool smaller(int *x,int *y)
{
for(int i=1;i<=ks;++i)
if(x[i]>=y[i])
return 0;
return 1;
}
int main()
{
freopen("BZOJ5124.in","r",stdin);
freopen("BZOJ5124.out","w",stdout);
scanf("%d%d",&ks,&n);
for(int i=1;i<=n;++i)
for(int j=1;j<=ks;++j)
scanf("%d",&a[i][j]);
scanf("%d",&m);
for(int i=1;i<=m;++i)
for(int j=1;j<=ks;++j)
scanf("%d",&b[i][j]);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=0;k<2;++k)
{
if(equal(a[i],b[j]))
{
int t=g[i][j][k^1];
if(!k)
(t+=1)%=mod;
(ans+=t)%=mod;
(f[i+1][j][k]+=t)%=mod;
}
if(f[i][j][k])
{
(f[i+1][j][k]+=f[i][j][k])%=mod;
if(!k)
{
if(bigger(a[i],b[j]))
(g[i][j+1][k]+=f[i][j][k])%=mod;
}
else
{
if(smaller(a[i],b[j]))
(g[i][j+1][k]+=f[i][j][k])%=mod;
}
}
if(g[i][j][k])
(g[i][j+1][k]+=g[i][j][k])%=mod;
}
printf("%d\n",ans);
return 0;
}
BZOJ5125小Q的书架
【题目】
原题地址
给定一个序列
a
a
,现在将序列分成段,每一段的代价是这个区间逆序对的个数,问分割后的最小代价。
【题目分析】
显然是个决策单调性dp,我就不平行四边形优化了,直接分治好了。
【解题思路】
首先显然对于连续一段排序的代价就是这段逆序对的个数,然后我们可以dp,设
fi,j表示将
f
i
,
j
表
示
将
[1,i]
分成
分
成
j$个连续段的最小代价即可。
这个dp显然又满足决策单调性,那么具有决策单调性的dp,可以直接平行四边形优化来做,当然我们很常见的还是分治求解。
用BIT维护一下区间逆序对个数即可。
你还可以选择用可持久化分块来达到更优的时间复杂度
【参考代码】
#include<bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
const int N=4e4+10;
int n,m,L,R,now;
int a[N],tr[N],f[N],g[N];
inline void _reset()
{
memcpy(g,f,sizeof(g));
memset(f,0x3f,sizeof(f));
memset(tr,0,sizeof(tr));
L=1;R=now=0;
}
inline int query(int x)
{
int ret=0;
for(;x;x-=lowbit(x))
ret+=tr[x];
return ret;
}
inline void update(int x,int v)
{
for(;x<=n;x+=lowbit(x))
tr[x]+=v;
}
inline void change(int l,int r)
{
while(R<r)
now+=(R-L+1-query(a[R+1])),update(a[++R],1);
while(L<l)
now-=query(a[L]-1),update(a[L++],-1);
while(L>l)
now+=query(a[L-1]-1),update(a[--L],1);
while(R>r)
now-=(R-L+1-query(a[R])),update(a[R--],-1);
}
inline void solve(int l,int r,int dl,int dr)
{
int mid=(l+r)>>1,dm=dl;
for(int i=dl;i<=min(dr,mid-1);++i)
{
change(i+1,mid);
int t=g[i]+now;
if(t<f[mid])
f[mid]=t,dm=i;
}
if(l<mid)
solve(l,mid-1,dl,dm);
if(r>mid)
solve(mid+1,r,dm,dr);
}
int main()
{
freopen("BZOJ5125.in","r",stdin);
freopen("BZOJ5125.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
update(a[i],1);
f[i]=f[i-1]+i-query(a[i]);
}
for(int i=2;i<=m;++i)
{
_reset();
solve(i,n,i-1,n);
}
printf("%d\n",f[n]);
return 0;
}
【总结】
关于决策点的dp真的是有很多玄学的优化姿势qwq。