zoukankan      html  css  js  c++  java
  • 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] imes j)

    其中(j-1le kle 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] eq a[r]&&lle 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;
    }
    
  • 相关阅读:
    浅谈c/c++中的指针问题
    谈谈八大排序算法问题
    隐藏在default construct后面的是什么
    浅谈编译过程和符号表重定位问题
    1.在VC编译器下面为什么每个头文件以及源文件都要包含“stdAfx.h”,那么stdAfx.h中到底存放了什么,用来做什么?
    成长从今天开始
    正则表达式
    安装RPM包或者安装源码包
    文档的压缩与打包
    文本编辑工具
  • 原文地址:https://www.cnblogs.com/jkzcr01-QAQ/p/13575233.html
Copyright © 2011-2022 走看看