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;
}
posted @ 2020-08-28 08:22  Chester1011  阅读(185)  评论(0编辑  收藏  举报
/*simplememory*/