DP练习题回顾(2)
被网络流虐了一天后,本蒟蒻又来做DP啦!(还是被虐
P4933 大师
一句话题意:求一个数列的等差子数列。
我看正解好像是\(n^2\)的,可蒟蒻的我只能想到\(n^3\),不过数据水,还是让我卡过去了
¥( ≧ ▽ ≦ )¥
开始讲算法吧:设\(f[i][j]\)为等差数列最后一项为\(i\),倒数第二项为\(j\)的方案数,这种状态好像有点奇怪,但还是可以做滴。
状态定义出来了,转移就简单了,三重循环暴力枚举,当\(a[i]-a[j]==a[j]-a[k]\)时,\(f[j][i]+=f[k][j]\),最后统计答案,欧了。
废话不多说,上代码:
#include<bits/stdc++.h>
#define re register
#define mod 998244353
using namespace std;
const int N=1005;
int n,a[N];
long long ans,f[N][N];
int main()
{
scanf("%d",&n);
for(re int i=1;i<=n;i++)scanf("%d",&a[i]);
for(re int i=1;i<=n;i++)
{
f[i][i]=1;ans=(ans+1)%mod;
for(re int j=1;j<i;j++)
{
f[j][i]=1;
for(re int k=j-1;k>0;k--)
{
if(a[i]-a[j]==a[j]-a[k])
f[j][i]=(f[j][i]+f[k][j])%mod;
}
ans=(f[j][i]+ans)%mod;
}
}
printf("%lld",ans);
return 0;
}
P5858 SWTR-03」Golden Sword
这题就比较好玩了,想了十来分钟,都只是一个\(O(nws)\)时间复杂度的算法,所以就只能想想优化了啦。
这题状态比较好想,\(f[i][j]\)为放完了前\(i\)个后,锅内还剩\(j\)个的耐久度最大值。同时转移方程也比较简单:
\(~~~~~~~~~~~~~~~~~~~~~~~~ f[i][j]=max~\){\(~f[i][k]~\)}\(~+a[i]\times j\)
其中\(j-1\le k\le j+s-1\)
然后你就会发现,\(TLE\)满天飞。
去哦德玛嘚!
求一个连续区间的最大值,而这个区间在一个大区间上滑动!咋这么眼熟呢?不就是滑动窗口吗?单调队列上!
在就没什么可讲了,至于单调队列优化,可以借鉴代码:
#include<bits/stdc++.h>
#define re register
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
#define inf 1e18
using namespace std;
const int N=5555;
int n,w,s,st[N],l,r;
long long a[N],que[N],f[N][N],ans=-inf;
int main()
{
scanf("%d%d%d",&n,&w,&s);
for(re int i=1;i<=n;i++)scanf("%lld",&a[i]);
memset(f,-63,sizeof(f));
f[0][0]=0;
for(re int i=1;i<=n;i++)
{
l=1,r=0;
for(re int j=0;j<=s;j++)
{
que[++r]=f[i-1][j],st[r]=j;
for(;r>l&&que[r]>=que[r-1];r--)st[r-1]=st[r],que[r-1]=que[r];
}
for(re int j=1;j<=min(w,i);j++)
{
if(st[l]<j-1)l++;
f[i][j]=que[l]+j*a[i];
if(j+s<=min(i,w))
{
que[++r]=f[i-1][j+s],st[r]=j+s;
for(;r>l&&que[r]>=que[r-1];r--)st[r-1]=st[r],que[r-1]=que[r];
}
}
}
// for(re int i=1;i<=n;i++)
// {
// for(re int j=1;j<=w;j++)
// printf("%d ",f[i][j]);
// puts("");
// }
for(re int i=1;i<=w;i++)ans=max(ans,f[n][i]);
printf("%lld",ans);
return 0;
}
P3205 [HNOI2010]合唱队
这题输入给的是排好队后的序列,因为不知道每个人是从哪边插入的队列,所以需要一维数组表示插入方向。
因此,状态就出来了,\(f[i][j][0/1]\)表示从\(i\)号位置至前\(j\)位的序列,且最后一个人是从哪个方向插入的(\(0\)表示左,\(1\)表示右
所以状态转移方程就出来了:
\[f[i][j][1]+=f[i-1][j-1][1]~~~~ a[i]>a[i-1]
\]
\[f[i][j][1]+=f[i-1][j-1][0]~~~~ a[i]>a[i-j+1]
\]
\[f[i][j][0]+=f[i][j-1][0]~~~~ a[i-j+1]<a[i-j+2]
\]
\[f[i][j][0]+=f[i][j-1][1]~~~~ a[i-j+1]<a[i]
\]
要注意的一点是:在\(j=2\)时,\(i-1=i-j+1\),会重复加,所以要特判。
手起,码落:
#include<bits/stdc++.h>
#define re register
#define mod 19650827
using namespace std;
const int N=1005;
int n,a[N],f[N][N][2],ans;
int main()
{
scanf("%d",&n);
for(re int i=1;i<=n;i++)scanf("%d",&a[i]);
for(re int i=1;i<=n;i++)f[i][1][0]=f[i][1][1]=1;
for(re int i=2;i<=n;i++)
{
if(a[i]>a[i-1])f[i][2][0]=f[i][2][1]=1;
for(re int j=3;j<=i;j++)
{
if(a[i]>a[i-1])(f[i][j][1]+=f[i-1][j-1][1])%=mod;
if(a[i]>a[i-j+1])(f[i][j][1]+=f[i-1][j-1][0])%=mod;
if(a[i-j+1]<a[i-j+2])(f[i][j][0]+=f[i][j-1][0])%=mod;
if(a[i-j+1]<a[i])(f[i][j][0]+=f[i][j-1][1])%=mod;
}
}
printf("%d",(f[n][n][0]+f[n][n][1])%mod);
return 0;
}
P4170 [CQOI2007]涂色
我是一个小画家,手拿画笔来画画~~
这题的状态应该也比较好想吧?\(f[l][r]\)表示\(l\)到\(r\)区间所需的最小填图数。而状态转移方程如下:
\[f[l][r]=min(f[l+1][r],f[l][r-1])~~~ a[l]=a[r]
\]
\[f[l][r]=min(f[l][k]+f[k+1][r])~~~ a[l]\neq a[r]\&\&l\le k<r
\]
第一个应该没问题,那第二个为什么呢?先来看看有什么性质吧,我们其实可以发现,最少的填图次数的方式,一定是如下的:
也就是说,填图不会跨过两个有不同颜色的区间。所以,一定存在一个点,把整个区间分开,而隔开数量不超过\(1\)的区间。
又讲完了,手起,码落:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=55;
int n,f[N][N];
char ch[N];
int main()
{
scanf("%s",ch+1);
n=strlen(ch+1);
memset(f,63,sizeof(f));
for(re int i=1;i<=n;i++)f[i][i]=1;
for(re int i=1;i<=n;i++)
for(re int j=i-1;j>0;j--)
if(ch[i]==ch[j]) f[j][i]=min(f[j+1][i],f[j][i-1]);
else for(re int k=j;k<i;k++)
f[j][i]=min(f[j][i],f[j][k]+f[k+1][i]);
printf("%d",f[1][n]);
return 0;
}
CF607B Zuma
好一个经典的游戏,好一个我不会写的题
其实这题和上一题差不了多少,可以自己想一想啦!(就是我自己不想打了
想好了可以对对代码:
#include<bits/stdc++.h>
#define re register
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
const int N=505;
int n,a[N],f[N][N];
int main()
{
scanf("%d",&n);
for(re int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(f,63,sizeof(f));
for(re int i=1;i<=n;i++)f[i][i]=1;
for(re int i=1;i<=n;i++)
for(re int j=i-1;j>0;j--)
{
if(a[i]==a[j])
{
if(i-j==1)f[j][i]=1;
else f[j][i]=f[j+1][i-1];
}
for(re int k=j;k<i;k++)
f[j][i]=min(f[j][i],f[j][k]+f[k+1][i]);
}
printf("%d",f[1][n]);
return 0;
}