您的位置:首页 > 其它

【dp-关于决策点】[Lydsy12月赛] BZOJ5124波浪序列 BZOJ5125小Q的书架

2018-02-22 21:20 302 查看
其实本来是不想写这篇博文的,但是5124这题没见过想写,单独写又有点短,于是乎多写一个凑数。

还有下面的原题地址是没有题面的,题面在这里

BZOJ5124波浪序列

【题目】

原题地址

给定两个XX维向量序列a[1,n],b[1,m]a[1,n],b[1,m],求有多少个序列f,gf,g满足1≤f1<f2<...<fk≤n,1≤g1<g2<...<gk≤m1≤f1<f2<...<fk≤n,1≤g1<g2<...<gk≤m且afi=bfi,[af1,af2,...,afk]afi=bfi,[af1,af2,...,afk]是波浪的(波浪指对于每个非两端ii满足ai−1<ai>ai+1ai−1<ai>ai+1或ai−1>ai<ai+1ai−1>ai<ai+1)

【题目分析】

显然是dp,但是没有见过,不会优化。

【解题思路】

首先有一个很显然的dp方法,我们令fi,j,kfi,j,k表示只考虑a[1..i]和b[1..j]a[1..i]和b[1..j],

选择的两个子序列结尾分别是ai和bjai和bj,上升下降状态为kk的方案数。

那么我们有fi,j,k=∑fx,y,1−kfi,j,k=∑fx,y,1−k,其中x<i,y<jx<i,y<j。暴力转移的复杂度是O(kn2m2)O(kn2m2)的,显然不能接受。

我们可以考虑将决策点转移的方案数先dp掉,转移后面我们就可以用O(1)O(1)进行转移。

那么令gi,y,kgi,y,k表示从fx,y,kfx,y,k作为决策点出发,当前要更新的是ii的方案数,

hi,j,khi,j,k表示从fx,y,kfx,y,k作为决策点出发,已经经历了gg的枚举,当前更新的是jj的方案数。

转移的话则是要么更新,要么将i或ji或j枚举到i+1以及j+1i+1以及j+1。

因为每次只有一个变量在动,所以另一个变量可以表示上一个位置的值,可以表示上一个位置的值,方便判断上升还是下降。

这样做的时间复杂度就可以优化到O(knm)O(knm)

【参考代码】

#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
[M],b
[M],f

[2],g

[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的书架

【题目】

原题地址

给定一个序列aa,现在将序列分成kk段,每一段的代价是这个区间逆序对的个数,问分割后的最小代价。

【题目分析】

显然是个决策单调性dp,我就不平行四边形优化了,直接分治好了。

【解题思路】

首先显然对于连续一段排序的代价就是这段逆序对的个数,然后我们可以dp,设fi,j表示将fi,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
,tr
,f
,g
;

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
);

return 0;
}


【总结】

关于决策点的dp真的是有很多玄学的优化姿势qwq。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: